三种IO模型 非阻塞IO和IO多路复用 5.22

三.IO模型

1.概念

在UNIX/Linux下主要有4种I/O 模型:

  1. 阻塞I/O:

    最常用、最简单、效率最低

  2. 非阻塞I/O:

    可防止进程阻塞在I/O操作上,需要轮询

  3. I/O 多路复用:

    允许同时对多个I/O进行控制

  4. 信号驱动I/O:

    一种异步通信模型

2.非阻塞IO

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OvNnQvHz-1684762191579)(C:\Users\孤独memories\AppData\Roaming\Typora\typora-user-images\image-20230522201906326.png)]

多次访问套接字缓冲区中有无数据,有数据就打印,没数据就返回

1)fcntl()

#include <unistd.h>
#include <fcntl.h>

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

作用:控制文件描述符
参数:
    fd --- 文件描述符
    cmd --- 命令
    	F_GETFL:获取文件状态标志值
    	F_SETFL:设置文件状态标志值
    arg --- 值
    	F_GETFL:忽略
    	F_SETFL:想要设置的文件状态标志值
返回值:
    F_GETFL:获取文件状态标志值
    F_SETFL:0
    失败:-1,并设置error
    
    
例:
    int flags;
	flags = fcntl(connfd,F_GETFL,0);//获取文件状态标志值
	flags |= O_NONBLOCK;//添加非阻塞标志
	fcntl(connfd,F_SETFL,flags);//设置文件状态标志值

注意:当设置非阻塞后,如果没有数据回收也会立即-1返回,有数据也能正常接收
/*===============================================
*   文件名称:server.c
*   创 建 者:memories 
*   创建日期:2023年05月18日
*   描    述:
================================================*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>


int main(int argc, char *argv[])
{ 
    //1.创建套接字
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(sockfd<0)
    {
        perror("socket");
        return -1;
    }
    printf("socket-----------------\n");
    //2.绑定本机地址和端口
     //用IPv4结构体
    struct sockaddr_in srvaddr;
	memset(&srvaddr,0,sizeof(srvaddr));
	srvaddr.sin_family = AF_INET;//指定地址族为IPv4的地址
	srvaddr.sin_port = htons(5420);//端口号
	srvaddr.sin_addr.s_addr = htonl(INADDR_ANY);//将点分式的字符串转化32位的网络字节序

	if(0 > bind(sockfd,(struct sockaddr*)&srvaddr,sizeof(srvaddr)))
    {
        perror("bind");
        return -1;
    }
    printf("bind-----------------\n");
    //3.设置监听套接字
    if(0>listen(sockfd,5))//必须写大于1的整数
    {
        perror("listen");
        return -1;
    }
    printf("listen-----------------\n");
    //4.接受客户端的连接,并生成通信套接字
    int connfd = accept(sockfd,NULL,NULL);
    if(connfd < 0)
    {
        perror("accept");
        return -1;
    }
    printf("accept-----------------\n");

    int flags = fcntl(connfd,F_GETFL,0);
    if(flags < 0)
    {
        perror("fcntl");
        return -1;
    }
    flags |= O_NONBLOCK;
    fcntl(connfd,F_SETFL,flags);

    //5.与客户端通信
    int ret;
    char buf[1024];
    while(1)
    {
        sleep(1);
        memset(buf,0,sizeof(buf));
        ret = read(connfd,buf,sizeof(buf));
        if(ret < 0)
        {
            perror("read");
            continue;
        }
        else if(ret == 0)
        {
            printf("write close\n");
            break;
        }
        printf("recv:%s\n",buf);

        if(0 > write(connfd,buf,ret))
        {
            perror("write");
            return -1;
        }
    }
    //6.关闭套接字
    close(sockfd);
    close(connfd);
    return 0;
} 


hqyj@ubuntu:~/5.22$ vi server.c
hqyj@ubuntu:~/5.22$ ./a.out 
socket-----------------
bind-----------------
listen-----------------
accept-----------------
read: Resource temporarily unavailable
read: Resource temporarily unavailable
read: Resource temporarily unavailable
read: Resource temporarily unavailable
read: Resource temporarily unavailable
read: Resource temporarily unavailable
read: Resource temporarily unavailable
read: Resource temporarily unavailable
read: Resource temporarily unavailable
recv:kjakd

3. IO多路复用

IO多路复用的步骤:

​ 1. 创建一张文件描述符的表(集合),把想要监测的文件描述符加入表中

​ 2. 监测是否有文件描述符产生事件

​ 3.判断是谁产生了事件

​ 4.处理事件

1)select()

#include <sys/select.h>

       /* According to earlier standards */
       #include <sys/time.h>
       #include <sys/types.h>
       #include <unistd.h>

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

作用:监测集合中是否有事件产生
参数:
    nfds --- 想要监测的最大文件描述符+1
    readfds --- 读事件集合
    writefds --- 写事件集合
    exceptfds --- 其他事件集合
    timeout --- 超时设置,NULL表示阻塞
    
     struct timeval {
               long    tv_sec;         /* seconds */
               long    tv_usec;        /* microseconds */
           };

    
返回值:
    <0:出错
    =0:超时设置时没有事件产生
    >0:有事件产生

void FD_CLR(int fd, fd_set *set); --- 把fd从集合中取出
int  FD_ISSET(int fd, fd_set *set);---判断fd是否还在集合中
void FD_SET(int fd, fd_set *set);---把fd添加到set集合中
void FD_ZERO(fd_set *set); --- 清除整个集合

/*===============================================
*   文件名称:server.c
*   创 建 者:     
*   创建日期:2023年05月18日
*   描    述:
================================================*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/select.h>
#include <sys/time.h>

int main(int argc, char *argv[])
{ 
    /*1. 创建套接字*/
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("socket");
        return -1;
    }
    printf("socket..............\n");

    /*2. 绑定本机地址和端口*/
    struct sockaddr_in srvaddr;
	memset(&srvaddr, 0, sizeof(srvaddr));
	srvaddr.sin_family 	= AF_INET;  //地址族
	srvaddr.sin_port 	= htons(6666); //端口号
    srvaddr.sin_addr.s_addr = htonl(INADDR_ANY);  //IP地址

	if (0 > bind(sockfd, (struct sockaddr*)&srvaddr, sizeof(srvaddr))) 
    {
        perror("bind");
        return -1;
    }
    printf("bind................\n");

    /*3. 设置监听套接字*/
    if (0 > listen(sockfd, 5))
    {
        perror("listen");
        return -1;
    }
    printf("listen.................\n");

	/*创建一张文件描述符表(集合), 把想要监测的文件描述符加入表中*/
	fd_set fds, rfds;
	FD_ZERO(&fds);
	FD_SET(0, &fds);  //把键盘加入集合中
	FD_SET(sockfd, &fds); //把监听套接字加入集合中
	int maxfd = sockfd;  //最大的文件描述符
	
	int ret, retval, i;
    char buf[1024];
	while (1)
	{
		rfds = fds;
		/*监测是否有文件描述符产生事件*/
		retval = select(maxfd+1, &rfds, NULL, NULL, NULL);
		if (retval < 0)
		{
			perror("select");
			break;
		}
		/*判断是谁产生了事件*/
		for (i = 0; i < maxfd+1; i++)
		{
			if (FD_ISSET(i, &rfds))
			{
				if (0 == i)  //键盘产生事件
				{
					fgets(buf, sizeof(buf), stdin);
					printf("Keyboard: %s", buf);
				}
				else if (sockfd == i) //监听套接字产生事件
				{
					/*4. 接受客户端的连接,并生成通信套接字*/
					int connfd = accept(sockfd, NULL, NULL);
					if (connfd < 0)
					{
						perror("accept");
						return -1;
					}
					printf("accept success!\n");
					//把通信套接字加入集合中
					FD_SET(connfd, &fds);
					maxfd = (maxfd > connfd) ? maxfd : connfd;
				}
				else //通信套接字
				{
					memset(buf, 0, sizeof(buf));
					ret = recv(i, buf, sizeof(buf), 0);
					if (ret < 0)
					{
						perror("recv");
					}
					else if (0 == ret)
					{
						printf("client close!\n");
						FD_CLR(i, &fds);  //把通信套接字去掉
						close(i);
					}
					else
					{
						printf("recv: %s\n", buf);   //把读取的数据显示在屏幕上
					}
				}
			}
		}
	}
   
    /*6. 关闭套接字*/
    close(sockfd);

    return 0;
} 


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m6QWis1B-1684762191581)(C:\Users\孤独memories\AppData\Roaming\Typora\typora-user-images\image-20230522202426455.png)]

2)poll()

#include <poll.h>

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

struct pollfd {
               int   fd;         /* file descriptor */
               short events;     /* requested events */
               short revents;    /* returned events */
           };
			POLLIN:有数据可读
            POLLOUT:有数据可写

作用:监测文件描述符集合中是否有事件产生
参数:
    fds --- 监测文件描述符集合:用结构体数组表示
    nfds --- 想要监测的文件描述符的个数
    timeout --- 超时设置,以毫秒为单位
        -1:阻塞
返回值:
    <0:出错
    =0:超时设置时没有事件产生
    >0:有事件产生

/*===============================================
*   文件名称:server.c
*   创 建 者:     
*   创建日期:2023年05月18日
*   描    述:
================================================*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/select.h>
#include <sys/time.h>
#include <poll.h>

int main(int argc, char *argv[])
{ 
    /*1. 创建套接字*/
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("socket");
        return -1;
    }
    printf("socket..............\n");

    /*2. 绑定本机地址和端口*/
    struct sockaddr_in srvaddr;
	memset(&srvaddr, 0, sizeof(srvaddr));
	srvaddr.sin_family 	= AF_INET;  //地址族
	srvaddr.sin_port 	= htons(6666); //端口号
    srvaddr.sin_addr.s_addr = htonl(INADDR_ANY);  //IP地址

	if (0 > bind(sockfd, (struct sockaddr*)&srvaddr, sizeof(srvaddr))) 
    {
        perror("bind");
        return -1;
    }
    printf("bind................\n");

    /*3. 设置监听套接字*/
    if (0 > listen(sockfd, 5))
    {
        perror("listen");
        return -1;
    }
    printf("listen.................\n");

	/*创建一张文件描述符表(集合), 把想要监测的文件描述符加入表中*/
	struct pollfd fds[1024];  //文件描述符集合
    fds[0].fd = 0;   	//把键盘添加到集合中
	fds[0].events = POLLIN; 
    fds[1].fd = sockfd;  //把监听套接字添加到集合中
    fds[1].events = POLLIN; 
	int nfds = 2;  //想要监测的文件描述符个数
	
	int ret, retval, i, j;
    char buf[1024];
	while (1)
	{
		/*监测是否有文件描述符产生事件*/
		retval = poll(fds, nfds, -1);
		if (retval < 0)
		{
			perror("poll");
			break;
		}
		/*判断是谁产生了事件*/
		for (i = 0; i < nfds; i++)   //i --- fds的下标
		{
			if (fds[i].revents & POLLIN)  //判断是否是i下标对应的文件描述符产生了事件
			{
				if (0 == fds[i].fd)  //键盘产生事件
				{
					fgets(buf, sizeof(buf), stdin);
					printf("Keyboard: %s", buf);
				}
				else if (sockfd == fds[i].fd) //监听套接字产生事件
				{
					/*4. 接受客户端的连接,并生成通信套接字*/
					int connfd = accept(sockfd, NULL, NULL);
					if (connfd < 0)
					{
						perror("accept");
						return -1;
					}
					printf("accept success!\n");
					//把通信套接字加入集合中
					for (j = 0; j < nfds; j++)
						if (-1 == fds[j].fd)
							break;
					fds[j].fd = connfd;
					fds[j].events = POLLIN; 
					if (j == nfds)
						nfds++;
				}
				else //通信套接字
				{
					memset(buf, 0, sizeof(buf));
					ret = recv(fds[i].fd, buf, sizeof(buf), 0);
					if (ret < 0)
					{
						perror("recv");
					}
					else if (0 == ret)
					{
						printf("client close!\n");
						close(fds[i].fd);
						fds[i].fd = -1; //把通信套接字去掉
					}
					else
					{
						printf("recv: %s\n", buf);   //把读取的数据显示在屏幕上
					}
				}
			}
		}
	}
   
    /*6. 关闭套接字*/
    close(sockfd);

    return 0;
} 



[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NlHoQBwD-1684762191582)(C:\Users\孤独memories\AppData\Roaming\Typora\typora-user-images\image-20230522202839227.png)]

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

孤独memories

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值