网络io与select


前言

本次是对网络部分知识的内容再次复习学习,同期文章可以看上篇Linux网络编程 ——Select机制
本文内容将以上课内容为导向,重新对其的学习。


一、什么是io

IO是输入input输出output的首字母缩写形式,直观意思是计算机输入输出,它描述的是计算机的数据流动的过程,因此IO第一大特征是有数据的流动;另外,对于一次IO,它究竟是输入还是输出,是针对不同的主体而言的,不同的主体有不同的描述。例如,甲乙两人交谈,甲将大脑中的想法通过声带震动,继而通过声波传入乙的耳朵,乙通过耳膜的震动再由神经将信号解析到大脑,这个数据流动的过程对甲而言是输出,对乙而言是输入。

下面,我们从三个层面来理解IO

从直观层面去理解IO

IO是计算机和外设之间的数据流动过程,电脑与外部设备如键盘和鼠标

从计算机架构的角度去理解IO

从计算机架构上来讲,任何涉及到计算机核心(CPU和内存)与其他设备间的数据转移的过程就是IO。本体就是计算机核心(CPU和内存)。例如从硬盘上读取数据到内存,是一次输入,将内存中的数据写入到硬盘就产生了输出。在计算机的世界里,这就是IO的本质。

从编程的角度去理解IO

此时,IO的主体是其应用程序的运行态,即进程,特别强调的是我们的应用程序其实并不存在实质的IO过程,真正的IO过程是操作系统的事情,这里把应用程序的IO操作分为两种动作:IO调用和IO执行。IO调用是由进程发起,IO执行是操作系统的工作。因此,更准确些来说,此时所说的IO是应用程序对操作系统IO功能的一次触发,即IO调用

以一个进程的输入类型的IO调用为例,它将完成或引起如下工作内容:
(1)进程向操作系统请求外部数据
(2)操作系统将外部数据加载到内核缓冲区
(3)操作系统将数据从内核缓冲区拷贝到进程缓冲区
(4)进程读取数据继续后面的工作

二、阻塞 与非阻塞

此学习以程序实例为导向:
阻塞程序如下:

#define MAXLNE  4096
int  main() {
    int listenfd,connfd;
    struct  sockaddr_in addr;
    char buf[MAXLNE];
    if((listenfd=socket(AF_INET,SOCK_STREAM,0))==-1) {
        printf("create socket failed: %s(errno:%d)\n",strerror(errno),errno);
        return 0;
    }
    memset(&addr,0,sizeof(&addr));
    addr.sin_family=AF_INET;
    //addr.sin_addr.s_addr=INADDR_ANY;
    addr.sin_port=htons(9999);
    inet_pton(AF_INET,"192.168.112.129",&addr.sin_addr.s_addr);
    if(bind(listenfd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
        printf("bind failed:%s(errno:%d)\n",strerror(errno),errno);
        return 0;
    }
    if(-1==listen(listenfd,128)) {

        printf("listen failed:%s(errno:%d)\n",strerror(errno),errno);
    }

    struct sockaddr_in client;
    socklen_t len=sizeof(client);
    if((connfd=accept(listenfd,(struct sockaddr *)&client,&len))==-1) {
        printf("accept failed:%s(errno:%d)\n",strerror(errno),errno);
        return 0;
    }
    printf("========waiting for client's request========\n");
    while(1) {
        int n=recv(connfd,buf,MAXLNE,0);
        printf("%d\n",n);
        if(n>0) {
            printf("recv msg:%s",buf);
            buf[n]='\0';
            send(connfd,buf,n,0);
            printf("aa");
        }
        else if(n==0) {
            close(connfd);
            break;
        }
    }
}

非阻塞程序如下:
添加上即可

 #include <fcntl.h> //需要包含的头文件
 int flag = fcntl(cfd, F_GETFL);
 flag |= O_NONBLOCK;
 fcntl(cfd, F_SETFL, flag);

注意:
(1)IO函数是否阻塞取决于连接的 fd 是否阻塞
(2)阻塞与非阻塞的主要区别在与是否等待系统内核数据就绪
阻塞与非阻塞模型如下所示:
在这里插入图片描述

可以看到,在阻塞io模型中,即使内核数据未准备好,应用程序中的read函数也在等待;而在非阻塞io模型中,应用程序检测到系统内核数据未就绪,直接返回继续执行应用程序中的任务,但是会一直轮询查看内核中数据是否就绪。

但是在数据拷贝阶段,两者都是阻塞的。

针对以上程序明显有弊端,只能有一个客户端连接请求进行通信
在这里插入图片描述

为了解决这问题,有以下方法:
解法: a.多线程 多进程
b. io多路复用
接下来我们进行代码复现

三、多线程复现

代码如下:

void *fun(void *arg) {
    int connfd=*(int *)arg;
    char buff[MAXLNE];
    while (1) {

		int n = recv(connfd, buff, MAXLNE, 0);
        if (n > 0) {
            buff[n] = '\0';
            printf("recv msg from client: %s\n", buff);

	    	send(connfd, buff, n, 0);
        } else if (n == 0) {
            close(connfd);
			break;
        }

	}

	return NULL;
}
while (1)
    {
        struct sockaddr_in cilent;
        socklen_t len=sizeof(cilent);
        if((connfd=accept(listenfd,(struct sockaddr *)&cilent,&len))==-1) {
            printf("accept:failed %s(errno:%d)",strerror(errno),errno);
        }

        pthread_t tid;
        int ret;
        if((ret=pthread_create(&tid,NULL,fun,(void *)&connfd))==-1)
        {
            
        }
       
    }

优点:逻辑简单
缺点:线程开销较大

四、io多路复用之select

原理看之前的文章。
这次直接上代码:

#define MAX_SIZE 1024
int main(int argc,char *argv[])
{
	char buf[MAX_SIZE];   int n;
    int sockfd=socket(AF_INET,SOCK_STREAM,0);
    sockaddr_in addr;
    memset(&addr,0,sizeof(addr));
    addr.sin_addr.s_addr=0;
    addr.sin_family=AF_INET;
    addr.sin_port=htons(8888);
    bind(sockfd,(struct sockaddr *)&addr,sizeof(addr));
    listen(sockfd,128);

	fd_set rfds,rset,wfds,wset;
    FD_ZERO(&rfds);
    FD_SET(sockfd,&rfds);
    FD_ZERO(&wfds);
    int max_fd=sockfd;
    while (1)
    {
        rset=rfds;
        wset=wfds;
        int ready=select(max_fd+1,&rset,&wset,NULL,NULL);
        
        if(FD_ISSET(sockfd,&rset))
        {
            struct sockaddr_in clientaddr;
            socklen_t len=sizeof(clientaddr);
            int cfd=accept(sockfd,(struct sockaddr *)&clientaddr,&len);
            FD_SET(cfd,&rfds);
            /**/
            if(cfd>max_fd)  max_fd=cfd;
            if(--ready==0)  continue;
        }

        for(int i=sockfd+1;i<=max_fd;i++)
        {

            if(FD_ISSET(i,&rset))
            {
                
                 n=recv(i,buf,MAX_SIZE,0);
                if(n>0)
                {
                    buf[n]='\0';
                    printf("recv msg from client: %s\n",buf);
                    FD_SET(i,&wfds);
                }
                else if(n==0)
                {
                    close(i);
                    FD_CLR(i,&rfds);
                    continue;
                }
                if(--ready==0)  break;
            }
            else if(FD_ISSET(i,&wset))
            {
                printf("n的个数 %d\n",n);
                send(i,buf,n,0);
                **FD_CLR(i,&wfds);**//非常重要
               // FD_SET(i,&rfds);
            }
        }

    }
	close(sockfd);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值