Bootstrap

day38 tcp 并发 ,linux下的IO模型----IO多路复用

TCP 并发

由于tcp协议只能实现一对一的通信模式。为了实现一对多,有以下的的处理方式

1. 多进程
开销大
效率低

2. 多线程
创建线程需要耗时
3. 线程池
多线程模型创建线程耗时问题,提前创建
4. IO多路复用
在不创建进程和线程的前提下,对多个文件描述符进行同时监测
IO:fd读写, 共用一个进程

1.多进程(服务端)

#include<stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>
#include <unistd.h>


int init_tpc_ser(const char *ip , unsigned short port)
{
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd < 0)
    {
        perror("fail socket");
        return -1;
    }
    struct sockaddr_in ser;
    ser.sin_family = AF_INET;
    ser.sin_port = htons(port);
    ser.sin_addr.s_addr = inet_addr(ip);

    int ret = bind(sockfd,(struct sockaddr *)&ser,sizeof(ser));
    if(ret < 0)
    {
        perror("fail bind ");
        return -1;
    }

    return sockfd;

}


void handler(int signo)
{
    wait(NULL);
}

int main(int agrc, char *agrv[])
{
    signal (17,handler);
    int sockfd = init_tpc_ser("192.168.0.179",50000);
    int ret = listen (sockfd,100);
    char buff[1024] = {0};
    if(ret < 0)
    {
        perror("fail listen");
        return -1;
    }

    while(1)
    {
        int connfd = accept(sockfd,NULL,NULL);
        if(connfd < 0)
        {
            perror("fail accept");
            return -1;
        }
        pid_t pid = fork();
        if(pid > 0)
        {

        }
        else if(pid == 0)
        {
            while(1)
            {
                memset(buff, 0 ,sizeof(buff));
                int ret = recv(connfd,buff ,sizeof(buff),0);
                printf("%s\n",buff);
                if(ret <= 0)
                {
                    return -1;
                }
                strcat(buff,"-----ok");
                ret = send(connfd, buff,sizeof(buff),0);
                if(ret < 0)
                {
                    return -1;
                }   
            }
        
            return 0;
        }

        else
        {
            printf("fork fail");
            return -1;
        }

    }
    close(sockfd);
    return-1;

}

2.多线程(服务端)

#include<stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>
#include <unistd.h>
#include <pthread.h>


int init_tpc_ser(const char *ip , unsigned short port)
{
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd < 0)
    {
        perror("fail socket");
        return -1;
    }
    struct sockaddr_in ser;
    ser.sin_family = AF_INET;
    ser.sin_port = htons(port);
    ser.sin_addr.s_addr = inet_addr(ip);

    int ret = bind(sockfd,(struct sockaddr *)&ser,sizeof(ser));
    if(ret < 0)
    {
        perror("fail bind ");
        return -1;
    }

    return sockfd;

}

    char buff[1024] = {0};
void* task(void *reg)
{
        int connfd = *(int *)reg;//传过来保存就不会收到其他线程影响
    while(1)
    {
        memset(buff, 0 ,sizeof(buff));
        int ret = recv(connfd,buff ,sizeof(buff),0);
        printf("%s\n",buff);
        if(ret == 0)
        {
            break;
        }
        strcat(buff,"-----ok");
        ret = send(connfd, buff,sizeof(buff),0);
        if(ret = 0)
        {
            break;
        }   
    }
    close(connfd);
    pthread_detach(pthread_self());//设置线程分离属性。主线程无暇回收次线程

    return 0;
}

int main(int agrc, char *agrv[])
{
    int sockfd = init_tpc_ser("192.168.0.179",50000);
    int ret = listen (sockfd,100);
    int connfd = 0;
    pthread_t tid = 0;
    
    if(ret < 0)
    {
        perror("fail listen");
        return -1;
    }

    while(1)
    {
         connfd = accept(sockfd,NULL,NULL);
        if(connfd < 0)
        {
            perror("fail accept");
            return -1;
        }
        pthread_create(&tid,NULL,task,&connfd); 
    }
    close(sockfd);
    return-1;

}

3.io多路复用(具体见下)

2.IO模型

Linux下常用的IO模型:

1. 阻塞IO

fgets
read
getchar
fread
fgetc
recv
recvfrom
1. 让多个IO事件以同步方式处理(顺序处理),
2. 多个IO事件之间互相影响
3. CPU占有率低

2. 非阻塞IO

   将IO对应的文件描述符设置成非阻塞方式。
    O_NONBLOCK
    fcntl

 1.非阻塞一般搭配轮询方式同时监测多个IO
 2.cpu占有率高
#include "head.h"


int main(int argc, const char *argv[])
{
	mkfifo("./fifo", 0664);	
	char buff[1024] = {0};
	
	int fifofd = open("./fifo", O_RDONLY);
	if (fifofd < 0)
	{
		perror("fail open fifo");
		return -1;
	}

	//1, 获取文件描述符(终端)原flags特性
	int flags = fcntl(0, F_GETFL);
	//2. 给原flags增加非阻塞特性
	flags = flags | O_NONBLOCK;
	//3. 给文件描述符设置新flags特性
	fcntl(0, F_SETFL, flags);


	while (1)
	{
		memset(buff, 0, sizeof(buff));
		fgets(buff, sizeof(buff), stdin);//不会堵塞在这里,内核检测到键盘输入自动运行
		printf("STDIN : %s\n", buff);

		memset(buff, 0, sizeof(buff));
		read(fifofd, buff, sizeof(buff));
		printf("FIFO : %s\n", buff);
	}

	close(fifofd);

	return 0;
}

3. 信号驱动IO

  SIGIO
 1.实现异步IO操作,节省CPU开销
 2.只能针对比较少的IO事件(信号很少)
#include "head.h"

char buff[1024] = {0};
void handler (int signo)
{
    memset(buff, 0, sizeof(buff));
    fgets(buff, sizeof(buff), stdin);
    printf("STDIN : %s\n", buff);

}
int main(int argc, const char *argv[])
{
    signal(SIGIO,handler);
    mkfifo("./fifo", 0664);	

    int fifofd = open("./fifo", O_RDONLY);
    if (fifofd < 0)
    {
        perror("fail open fifo");
        return -1;
    }

    //1, 获取文件描述符原flags特性
    int flags = fcntl(0, F_GETFL);
    //2. 给原flag异步
    flags = flags |O_ASYNC;

    //3. 给文件描述符设置新flags特性
    fcntl(0, F_SETFL, flags);
    //关联信号与当前进程
    fcntl(0,F_SETOWN,getpid());


        while (1)
        {

            memset(buff, 0, sizeof(buff));
            read(fifofd, buff, sizeof(buff));
            printf("FIFO : %s\n", buff);
        }

    close(fifofd);

    return 0;
}

4. IO多路复用(IO多路转接)

   在不创建新的进程和线程的前提下, 在一个进程中,同时监测多个IO
1. select
      1. 文件描述符集合以数组(位图)的方式保存,最多监测1024个文件描述符。
      2. 文件描述符集合创建在应用层,需要应用层和内核层反复拷贝
      3. 内核层返回整个文件描述符集合,需要用户层遍历查找到达事件的文件描述符
      4. 只能工作在水平触发模式(低速模式)
2. poll
      1. 文件描述符集合以链表的方式保存,监测文件描述符可超过1024。
      2. 文件描述符集合创建在应用层,需要应用层和内核层反复拷贝
      3. 内核层返回整个文件描述符集合,需要用户层遍历查找到达事件的文件描述符
      4. 只能工作在水平触发模式(低速模式)
3. epoll 
      1. 文件描述符集合以树型结构(红黑树)保存,监测文件描述符可超过1024, 提高了数据的查找速度。
      2. 文件描述符集合创建在内核层;
      3. 返回的是到达事件的文件描述符集合,无需遍历查找
      4. 可以工作在水平触发模式(低速模式),也可以工作在边沿触发模式(高速模式)

1.select

  1. 创建文件描述符集合
    2. 添加关注的文件描述符到集合
    3. 传递给内核,内核开始监测IO事件 select
    4. IO事件到达则返回结果–》
    2.哪个fd事件到达执行哪个
int select(int nfds, fd_set *readfds, fd_set *writefds,
                  fd_set *exceptfds, struct timeval *timeout);
功能:通知内核监测IO事件并返回事件结果
参数:
        nfds:最大文件描述符+1
        readfds: 文件描述符读事件集合
        writefds:文件描述符写事件集合
        exceptfds:其他
        timeout : 超时时间
返回值:
    成功:返回达到的事件的个数
    失败:-1
    设置超时:超时时间到,但没有事件到达:0

       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);

#include "head.h"


int main(int argc, const char *argv[])
{
	mkfifo("./fifo", 0664);	
	char buff[1024] = {0};
	
	int fifofd = open("./fifo", O_RDONLY);
	if (fifofd < 0)
	{
		perror("fail open fifo");
		return -1;
	}
	
    int maxfd = 0;
    fd_set rdfds;//创建集合
    FD_ZERO(&rdfds);//清0
    //加入集合
    FD_SET(0,&rdfds);
    FD_SET(fifofd,&rdfds);
    maxfd = fifofd >maxfd ?fifofd:maxfd;

    fd_set rdfdscopy;

    while(1)
    {
        rdfdscopy= rdfds;//防止rdfds被修改,
        //发送位图给内核
        int cnt = select(maxfd+1,&rdfdscopy,NULL,NULL,NULL);
        if(cnt < 0)
        {
            perror("fail select");
            return -1;
        }
        //判断是否事件到达
        if(FD_ISSET(0,&rdfdscopy))
        {
            memset(buff,0,sizeof(buff));
            fgets(buff,sizeof(buff),stdin);
            printf("stdin : %s",buff);
        }
        if(FD_ISSET(fifofd,&rdfdscopy))
        {
            memset(buff,0,sizeof(buff));
            read(fifofd,buff,sizeof(buff));
            printf("fifo : %s\n",buff);
        }

    }
	close(fifofd);

	return 0;
}

select实现并发tcp

#include "head.h"


int init_tcp_ser(const char *ip, unsigned short port)
{
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (sockfd < 0)
	{
		perror("fail socket");
		return -1;
	}
	
	struct sockaddr_in ser;
	ser.sin_family = AF_INET;
	ser.sin_port = htons(port);
	ser.sin_addr.s_addr = inet_addr(ip);
	int ret = bind(sockfd, (struct sockaddr *)&ser, sizeof(ser));
	if (ret < 0)
	{
		perror("fail bind");
		return -1;
	}

	ret = listen(sockfd, 100);
	if (ret < 0)
	{
		perror("fail listen");
		return -1;
	}
	
	return sockfd;
}	



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

	char buff[1024] = {0};
	int sockfd = init_tcp_ser("192.168.0.166", 50000);	
	if (sockfd < 0)
	{
		return -1;
	}
	
	struct sockaddr_in cli;
	socklen_t clilen = sizeof(cli);
	
	int maxfd = 0;
	//1、 创建文件描述符集合
	fd_set rdfds, rdfdstmp;
	FD_ZERO(&rdfds);
	
	//2. 将关注的监听套接字加入到集合
	FD_SET(sockfd, &rdfds);
	maxfd = sockfd;

	while (1)
	{
		//3. 内核开始检测套接字事件
		rdfdstmp = rdfds;
		int cnt = select(maxfd+1, &rdfdstmp, NULL, NULL, NULL);
		if (cnt < 0)
		{
			perror("fail select");
			return -1;
		}
		// 是否有监听套接字事件到达
		if (FD_ISSET(sockfd, &rdfdstmp))
		{
			int connfd = accept(sockfd, (struct sockaddr *)&cli, &clilen);
			if (connfd < 0)
			{
				perror("fail accept");
				return -1;
			}
			printf("[%s : %d] get online!\n", inet_ntoa(cli.sin_addr), ntohs(cli.sin_port));
			
			FD_SET(connfd, &rdfds);
			maxfd = maxfd > connfd ? maxfd : connfd;
		}
		



		//循环遍历剩余套接字,是否有通讯套接字事件到达
		for (int i = sockfd+1; i <= maxfd; i++)
		{
			if (FD_ISSET(i, &rdfdstmp))
			{
				memset(buff, 0, sizeof(buff));
				ssize_t size = recv(i, buff, sizeof(buff), 0);
				if (size < 0)
				{
					perror("fail recv");
					FD_CLR(i, &rdfds);
					close(i);
					continue;
				}
				else if (0 == size)
				{
					FD_CLR(i, &rdfds);
					close(i);
					continue;
				}
				printf("%s\n", buff);
				strcat(buff, "-----ok!");
				size  = send(i, buff, strlen(buff), 0);
				if (size < 0)
				{
					perror("fail recv");
					FD_CLR(i, &rdfds);
					close(i);
					continue;				
				}
			}
		}


	}
	
	close(sockfd);

	return 0;
}

;