day34

1    非阻塞型IO


让我们的read函数不再阻塞,无论是否读取到消息,立刻返回


1.1    fcntl函数

原型:int fcntl(int fd, int cmd, ... /* arg */ );
调用:int flag = fcntl(描述符,F_GETFL)
     fcntl(描述符,F_SETFL,flag)
功能描述:设置或者获取文件的各项属性,到底如何操作由cmd决定,一般我们都会用来设置阻塞或者非阻塞IO
参数解析:
    参数 fd:准备设置属性的文件的描述符
    参数 cmd:文件到底设置什么属性又cmd决定
    参数 ...:
        F_SETFL:设置文件的flag属性
        F_GETFL:获取当前文件的flag属性

2    多路文件IO


能够实现效果:先发生输入事件,再去调用阻塞型的读取函数
例如:
正常情况下,都是先调用scanf函数,阻塞并等待键盘输入事件
如果使用了多路文件IO的话,就能实现:先发生键盘输入事件,事件发生之后,立刻调用scanf函数,直线了高效率且非阻塞
 


2.1    多路文件IO的工作原理


内核会监视目标套接字的缓存区变化:如果
①    缓存区发生了改变:边缘触发
②    缓存区存在数据:水平触发
内核就会通知监视者,有描述符是可读的
已下3个模型,都是上述工作方式
 


2.2    select 模型

原型:int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
 
调用:select(FD_SETSIZE,描述符集合,0,0,0)
FD_SETSIZE:值为1024
 
功能描述:以阻塞的形式监视 readfds,writefds,exceptfds 这3个描述符集合中,所有描述符,如果有任何描述符激活,则select解除阻塞
 
参数解析:
    参数 nfds:readfds,writefds,exceptfds 这3个集合中的最大值
    参数 readfds:监视描述符集合中任意的描述符是否可读,一般我们只用这个
    参数 writefds:监视描述符集合中任意的描述符是否可写,一般写NULL
    参数 exceptfds:监视描述符集合中任意的描述符是否发生意外,一般写NULL
        注意:只要select监视到了有描述符激活,就会将激活的描述符,以覆盖的形式写入到上述3个fds里面去
    
    参数 timeout:是一个结构体,结构如下
        struct timeval {
            long    tv_sec;         /* seconds */
            long    tv_usec;        /* microseconds */
        };
        表示select函数只阻塞传入的时间长度的秒数,超过这个时间自动解除阻塞
        传NULL表示:一直阻塞,不受时间影响
返回值:返回激活的描述符的数量
 
如何操作描述符集合

void FD_CLR(int fd, fd_set *set);
    功能描述: 从 set 中删除描述符 fd
int  FD_ISSET(int fd, fd_set *set);
    功能描述:判断 set 中是否存在描述符 fd
    返回值:如果存在返回1,不存在返回0
void FD_SET(int fd, fd_set *set);
    功能描述:将描述符 fd 添加到 set 里面去
void FD_ZERO(fd_set *set);
    功能描述:清空 set 所有描述符,相当于初始化的功能

select多并发服务器

select代码模型

fd_set readfds;
FD_ZERO(readfds)
FD_SET(想要监视的描述符,readfds)
while(1){
    select()
    判断/循环判断哪个描述符激活了{
        调用对用的阻塞函数
        例如:accept 或者 read    
    }
}
 

如何操作描述符集合

void FD_CLR(int fd, fd_set *set);
    功能描述: 从 set 中删除描述符 fd
int  FD_ISSET(int fd, fd_set *set);
    功能描述:判断 set 中是否存在描述符 fd
    返回值:如果存在返回1,不存在返回0
void FD_SET(int fd, fd_set *set);
    功能描述:将描述符 fd 添加到 set 里面去
void FD_ZERO(fd_set *set);
    功能描述:清空 set 所有描述符,相当于初始化的功能

select多并发服务器


2.3    poll模型


工作方式和select是一样的,都是用来监视描述符是否激活,只不过操作过程不一样
因为select的不足,才会有poll模型的
① select中 fd_set 类型大小固定,1024个,如果要监视的描述符数量超过1024个,就会有问题
② select监视到激活的描述符成功后,会将激活的把readfds覆盖
poll 解决了上述2个问题

原型:int poll(struct pollfd *fds, nfds_t nfds, int timeout);
调用:poll(准备监视的struct pollfd 数组,数组的容量/实际长度,-1)
功能描述:监视 fds所指向的描述符数组中所有描述符的情况,最多监视nfds个,一般就是数组的容量
参数解析:
    参数 fds:结构体数组,数组中的每一个结构体元素都是一个描述符搭配一些其他数据,结构如下
        struct pollfd {
            int   fd;         /* file descriptor */要监视的描述符
            short events;     /* requested events */可读、可写、意外
                因为可读的原因激活:POLLIN,一般我们都写这个
                因为可写的原因激活:POLLOUT
            short revents;    /* returned events */
                fd描述符,一旦激活,用来表示具体因为什么原因激活的                                   
        };
    参数 nfds:想要监视的描述符的数量,一般就是fds这个数组的实际长度
    参数 timeout:poll函数阻塞时长,单位为毫秒
        传 0 表示不阻塞
        传 -1 表示一直阻塞,直到有描述符激活
        
返回值:成功返回激活的描述符的数量                        
注意:poll函数监视的直接是一个结构体数组,这个数组我们是可以直接操作的,不需要额外的函数去操作
注意:poll的激活方式为水平触发
    水平触发:只要监视的所有套接字中,有任何套接字缓存区存在数据,则poll就会激活                

poll的代码模型

struct pollfd fds[n] = {0};
fds[0].fd = 想要监视的第一个描述符
fds[0].events = POLLIN
 
while(1){
    poll(fds,n,-1)
    for(遍历 fds){
        // 判断 fds中哪个描述符激活了
        if(fds[i].revents == POLLIN){
            调用对应的阻塞函数
            例如 accept 或者 read 或者 scanf等        
        }                        
    }
}



    服务器和2个客户端互相聊天,服务器和客户端都需要使用select模型去实现
    在不开线程的情况下,实现互相聊天

服务端

#include <myhead.h>
#define SER_PORT 6666		   //服务器端口号
#define SER_IP "192.168.0.129" //服务器ip地址

int main(int argc, const char *argv[])
{

	int client_arr[100] = {0};
	int len = 0;
	//1、创建套接字
	int sfd = socket(AF_INET, SOCK_STREAM, 0);
	//参数1:表示ipv4的网络通信
	//参数2:表示使用的是TCP通信方式
	//参数3:表示默认使用一个协议
	if (sfd == -1)
	{
		perror("socket error");
		return -1;
	}
	printf("socket success, sfd = %d\n", sfd); //3

	//将端口号快速重用
	int reuse = 1;
	if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1)
	{
		perror("setsockopt error");
		return -1;
	}
	printf("端口号快速重用成功\n");

	//2、为套接字绑定ip地址和端口号
	//2.1 填充地址信息结构体
	struct sockaddr_in sin;
	sin.sin_family = AF_INET;				 //通信域
	sin.sin_port = htons(SER_PORT);			 //端口号
	sin.sin_addr.s_addr = inet_addr(SER_IP); //ip地址

	//2.2 绑定工作
	if (bind(sfd, (struct sockaddr *)&sin, sizeof(sin)) == -1)
	{
		perror("bind error");
		return -1;
	}
	printf("bind success\n");

	//3、将套接字设置成被动监听状态
	if (listen(sfd, 128) == -1)
	{
		perror("listen error");
		return -1;
	}
	printf("listen success\n");

	//4、阻塞等待客户端的连接请求
	//4.1 定义变量用于接收客户端的信息
	fd_set rdset;
	FD_ZERO(&rdset);
	FD_SET(sfd, &rdset);
	FD_SET(STDIN_FILENO, &rdset);
	while (1)
	{

		fd_set temp = rdset;
		select(FD_SETSIZE, &temp, 0, 0, 0);
		if (FD_ISSET(sfd, &temp))
		{
			int client = accept(sfd, 0, 0);
			printf("有新客户端链接\n");
			FD_SET(client, &rdset);
			client_arr[len] = client;
			len++;
		}
		else if (FD_ISSET(STDIN_FILENO, &temp))
		{
			char buf[128] = "";
			printf("请输入>>>");
			fgets(buf, sizeof(buf), stdin);
			buf[strlen(buf) - 1] = 0;
			for (int i = 0; i < len; i++)
			{
				int client = client_arr[i];
				send(client, buf, 128, 0);
			}
		}
		for (int i = 0; i < len; i++)
		{
			int client = client_arr[i];
			if (FD_ISSET(client, &temp))
			{
				char buf[128] = "";
				int res = read(client, buf, 128);
				if (res == 0)
				{
					printf("有客户端断开\n");
					FD_CLR(client, &rdset);
					for (int j = i; j < len - 1; j++)
					{
						client_arr[j] = client_arr[j + 1];
					}
					len--;
					close(client);
					break;
				}
				printf("%s\n", buf);
			}
		}
	}
	return 0;
}

客户端

#include <myhead.h>
#define SER_POPT 6666
#define SER_IP "192.168.0.129"
#define CLI_POPT 8888
#define CLI_IP "192.168.0.129"
int main(int argc, const char *argv[])
{

    int cfd = socket(AF_INET, SOCK_STREAM, 0);
    if (cfd == -1)
    {
        perror("socket error");
        return -1;
    }
    printf("cfd =%d\n", cfd);
    int reuse = 1;
    if (setsockopt(cfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1)
    {
        perror("reuse error");
        return -1;
    }
    struct sockaddr_in cin;
    cin.sin_family = AF_INET;                //通信域     //端口号
    cin.sin_addr.s_addr = inet_addr(CLI_IP); //ip地址

    //2.2 绑定工作
    if (bind(cfd, (struct sockaddr *)&cin, sizeof(cin)) == -1)
    {
        perror("bind error");
        return -1;
    }
    printf("bind success\n");
    struct sockaddr_in sin;
    sin.sin_family = AF_INET;
    sin.sin_port = htons(SER_POPT);
    sin.sin_addr.s_addr = inet_addr(SER_IP);

    if (connect(cfd, (struct sockaddr *)&sin, sizeof(sin)) == -1)
    {
        perror("connect error");
        return -1;
    }
    fd_set rdset;
    FD_ZERO(&rdset);
    FD_SET(cfd, &rdset);
    FD_SET(STDIN_FILENO, &rdset);
    while (1)
    {
        fd_set temp = rdset;
        select(FD_SETSIZE, &temp, 0, 0, 0);
        if (FD_ISSET(cfd, &temp))
        {
            char buf[128] = "";
            recv(cfd, buf, 128, 0);
            printf("%s\n", buf);
        }
        else if (FD_ISSET(0, &temp))
        {
            char buf[128] = "";
            printf("请输入>>>");
            fgets(buf, sizeof(buf), stdin);
            buf[strlen(buf) - 1] = 0;
            send(cfd, buf, strlen(buf), 0);
            printf("success\n");
        }
    }

    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值