TCP并发模型——计算机网络——day04

今天主要学习了TCP并发模型,并且理解了TCP多线程模型的缺点,最主要的就是:创建线程会带来资源开销,能够实现的并发量比较有限

IO模型

IO模型主要分为4类,分别是:
1.阻塞IO:
	没有数据到来时,可以让任务挂起,节省CPU资源开销,提高系统效率
2.非阻塞IO:
	程序未接收到数据时一直执行,效率很低
3.异步IO:
	只能绑定一个文件描述符用来读取数据
4.多路复用IO:
	select
        1.select监听的集合中的文件描述符有上限限制
        2.select有内核层向用户层数据空间拷贝的过程,占用系统资源开销
        3.select必须轮询检测产生事件的文件描述符
        4.select只能工作在水平触发模式(低速模式),无法工作在边沿触发(高速模式)

    poll
        1.poll有内核层向用户层数据空间拷贝的过程,占用系统资源开销
        2.poll必须轮询检测产生事件的文件描述符
        3.poll只能工作在水平触发模式(低速模式),无法工作在边沿触发(高速模式)

    epoll :这个明天来讲

主要介绍一下关于TCP多路复用IO

在多路复用中,其client.c一样,所以在这里把代码写一下,在下面只写客户端的代码

client.c代码
c 复制代码
#include "head.h"

int CreateTcpClient(char *pip, int port)	//链接tcp
{
	int ret = 0;
	int sockfd = 0;
	struct sockaddr_in seraddr;

	sockfd = socket(AF_INET, SOCK_STREAM, 0);	//参数1:IPv4协议族;参数2:数据报套接字;参数3:协议,默认为0
	if (-1 == sockfd)
	{
		perror("fail to socket");
		return -1;
	}
	
	seraddr.sin_family = AF_INET;	//IPv4模式
	seraddr.sin_port = htons(port);	//将端口50000转为网络的大端模式
	seraddr.sin_addr.s_addr = inet_addr(pip);	//将字符串IP地址转为内存中的IP地址
	ret = connect(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
	if (-1 == ret)
	{
		perror("fail to connect");
		return -1;
	}
	
	return sockfd;
}

int main(void)
{
	int sockfd = 0;
	char tmpbuff[4096] = {0};
	int cnt = 0;
	ssize_t nsize = 0;

	sockfd = CreateTcpClient("192.168.1.152", 50000);
	
	while (1)
	{
		memset(tmpbuff, 0, sizeof(tmpbuff));	//清零操作
		sprintf(tmpbuff, "hello world --- %d", cnt);
		cnt++;
		nsize = send(sockfd, tmpbuff, strlen(tmpbuff), 0);	//发送
		if (-1 == nsize)
		{
			perror("fail to send");
			return -1;
		}

		memset(tmpbuff, 0, sizeof(tmpbuff));
		nsize = recv(sockfd, tmpbuff, sizeof(tmpbuff), 0);	//接收
		if (-1 == nsize)
		{
			perror("fail to recv");
			return -1;
		}

		printf("RECV:%s\n", tmpbuff);
	}

	close(sockfd);

	return 0;
}

select

c 复制代码
select 
  int select(int nfds, fd_set *readfds, fd_set *writefds,
              fd_set *exceptfds, struct timeval *timeout);
  功能:
    select监听文件描述符集合中是否有文件描述编程ready状态
  功能:
    nfds:最大文件描述符的值+1 
    readfds:读文件描述符集合
    writefds:写文件描述符集合
    exceptfds:其余文件描述符集合
    timeout:等待的时长
        NULL 一直等待
  返回值:
    成功返回文件描述符集合中的文件描述符个数
    失败返回-1 



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加入到集合中

void FD_ZERO(fd_set *set);
功能:
    将文件描述符集合清0 

eg:

server.c代码

c 复制代码
#include"head.h"

int CreateListenSocket(char *pip,int port)
{
	int ret = 0;
	int sockfd = 0;
	struct sockaddr_in seraddr;

	sockfd = socket(AF_INET,SOCK_STREAM,0);
	if(-1 == sockfd)
	{
		perror("fail to socket");
		return -1;
	}

	seraddr.sin_family = AF_INET;
	seraddr.sin_port = htons(port);
	seraddr.sin_addr.s_addr = inet_addr(pip);
	ret = bind(sockfd,(struct sockaddr *)&seraddr,sizeof(seraddr));
	if(-1 == ret)
	{
		perror("fail to bind");
		return -1;
	}

	ret = listen(sockfd,10);
	if(-1 == ret)
	{
		perror("fail to listen");
		return -1;
	}

	return sockfd;
}

int HandleTcpClient(int confd)
{
	char tmpbuff[4096] = {0};
	ssize_t nsize = 0;

	memset(tmpbuff,0,sizeof(tmpbuff));
	nsize = recv(confd,tmpbuff,sizeof(tmpbuff),0);
	if (-1 == nsize)
	{
		perror("fail to recv");
		return -1;
	}
	else if (0 == nsize)
	{
		return 0;
	}

	sprintf(tmpbuff, "%s ----echo", tmpbuff);
	nsize = send(confd, tmpbuff, strlen(tmpbuff), 0);
	if (-1 == nsize)
	{
		perror("fail to send");
		return -1;
	}

	return nsize;
}

int main(void)
{
	int sockfd = 0;
	int confd = 0;
	fd_set rdfds;
	fd_set tmpfds;
	int maxfd = 0;
	int ret = 0;
	int i = 0;


	sockfd = CreateListenSocket("192.168.1.152",50000);

	FD_ZERO(&rdfds);	// 将文件描述符集合清0 
	FD_SET(sockfd,&rdfds);	//将文件描述符socket加入到集合中
	maxfd = sockfd;

	while(1)
	{
		tmpfds = rdfds;
		ret = select(maxfd+1,&tmpfds,NULL,NULL,NULL);	//select监听文件描述符集合中是否有文件描述编程ready状态
		if(-1 == ret)
		{
			perror("fail to select");
			return -1;
		}

		if(FD_ISSET(sockfd,&tmpfds))	//判断文件描述符fd是否仍在集合中
		{
			confd = accept(sockfd,NULL,NULL);
			if(-1 == confd)
			{
				perror("fail to accept");
				FD_CLR(sockfd,&rdfds);	//将文件描述符sockfd从集合中清除 

				close(sockfd);
				continue;
			}

			FD_SET(confd,&rdfds);	//将文件描述符sonfd加入到集合中
			maxfd = maxfd > confd ? maxfd : confd;
		}

		for(i = sockfd + 1;i <= maxfd;++i)
		{
			if(FD_ISSET(i,&tmpfds))
			{
				ret = HandleTcpClient(i);
				if(-1 == ret)
				{
					fprintf(stderr,"handle client failed!\n");
					FD_CLR(i,&rdfds);
					close(i);
					continue;
				}
				else if(0 == ret)
				{
					fprintf(stderr,"client disconnected!\n");
					FD_CLR(i,&rdfds);
					close(i);
					continue;
				}
			}
		}
	}

	close(confd);
	close(sockfd);

	return 0;
}

结果:

select缺点:
	1.select监听的集合中的文件描述符有上限限制
	2.select有内核层向用户层数据空间拷贝的过程,占用系统资源开销
	3.select必须轮询检测产生事件的文件描述符
	4.select只能工作在水平触发模式(低速模式),无法工作在边沿触发(高速模式)

poll

c 复制代码
poll  
      int poll(struct pollfd *fds, nfds_t nfds, int timeout);
      功能:
        监听文件描述符集合是否有事件发生
      参数:
        fds:监听文件描述符集合数组空间首地址
        nfds:监听文件描述符集合元素个数
        timeout:等待的时间(-1 一直等待)
      返回值:
        成功返回产生事件的文件描述符个数
        失败返回-1 

    struct pollfd {
        int   fd;         /* file descriptor */
        short events;     /* requested events */
        short revents;    /* returned events */
    };

    fd:监听的文件描述符
    events:要监听的事件  POLLIN:是否可读  POLLOUT:是否可写
    revents:实际产生的事件 

eg:server.c

c 复制代码
#include"head.h"

int CreateListenSocket(char *pip,int port)
{
	int ret = 0;
	int sockfd = 0;
	struct sockaddr_in seraddr;

	sockfd = socket(AF_INET,SOCK_STREAM,0);
	if(-1 == sockfd)
	{
		perror("fail to socket");
		return -1;
	}
	
	seraddr.sin_family = AF_INET;
	seraddr.sin_port = htons(port);
	seraddr.sin_addr.s_addr = inet_addr(pip);
	ret = bind(sockfd,(struct sockaddr *)&seraddr,sizeof(seraddr));
	if(-1 == ret)
	{
		perror("fail to bind");
		return -1;
	}

	ret = listen(sockfd,10);
	if(-1 == ret)
	{
		perror("fail to listen");
		return -1;
	}

	return sockfd;
}

int HandleTcpClient(int confd)
{
	char tmpbuff[4096] = {0};
	ssize_t nret = 0;

	memset(tmpbuff,0,sizeof(tmpbuff));
	nret = recv(confd,tmpbuff,sizeof(tmpbuff),0);
	if(-1 == nret)
	{
		perror("fail to recv");
		return -1;
	}
	else if(0 == nret)
	{
		return 0;
	}

	sprintf(tmpbuff,"%s-----------echo",tmpbuff);
	nret = send(confd,tmpbuff,strlen(tmpbuff),0);
	if(-1 == nret)
	{
		perror("fail to send");
		return -1;
	}

	return nret;
}

int InitFds(struct pollfd *fds,int maxlen)	//将文件描述符集合数组中fd置为-1
{
	int i = 0;
	for(i = 0;i < maxlen;++i)
	{
		fds[i].fd = -1;
	}
	
	return 0;
}

int AddFd(struct pollfd *fds,int maxlen,int fd,short env)	//加入
{
	int i = 0;
	for(i = 0;i < maxlen;++i)
	{
		if(fds[i].fd == -1)
		{
			fds[i].fd = fd;
			fds[i].events = env;

			break;
		}
	}

	if(i == maxlen)
	{
		return -1;
	}

	return 0;
}

int DeleteFd(struct pollfd *fds,int maxlen,int fd)	//删除
{
	int i = 0;
	for(i = 0;i < maxlen;++i)
	{
		if(fds[i].fd == fd)
		{
			fds[i].fd = -1;
			break;
		}
	}

	return 0;
}

int main(void)
{
	int sockfd = 0;
	int confd = 0;
	struct pollfd fds[1024];	//创建文件描述符集合数组
	int nready = 0;
	int i = 0;
	int ret = 0;
	
	sockfd = CreateListenSocket("192.168.1.152",50000);
	
	InitFds(fds,1024);		//初始化
	AddFd(fds,1024,sockfd,POLLIN);	

	while(1)
	{
		nready = poll(fds,1024,-1);	//监听文件描述符集合是否有事件发生
		if(-1 == nready)
		{
			perror("fail to poll");
			return -1;
		}

		for(i = 0;i < 1024;++i)
		{
			if(fds[i].fd == -1)	
			{
				continue;
			}

			if(fds[i].revents & POLLIN && fds[i].fd == sockfd)
			{
				confd = accept(sockfd,NULL,NULL);
				if(confd == -1)
				{
					perror("fail to accept");
					DeleteFd(fds,1024,sockfd);
					close(sockfd);
					continue;
				}

				AddFd(fds,1024,confd,POLLIN);
			}
			else if(fds[i].revents & POLLIN && fds[i].fd != sockfd)
			{
				ret = HandleTcpClient(fds[i].fd);
				if(-1 == ret)
				{
					fprintf(stderr,"handle tcp client failed!\n");
					close(fds[i].fd);
					DeleteFd(fds,1024,fds[i].fd);
					continue;
				}
				else if(0 == ret)
				{
					fprintf(stderr,"client disconnected!\n");
					close(fds[i].fd);
					DeleteFd(fds,1024,fds[i].fd);
					continue;
				}
			}
		}
	}

	close(sockfd);

	return 0;
}
运行结果与select类似,但是缺点少了一点:
poll
	1.poll有内核层向用户层数据空间拷贝的过程,占用系统资源开销
    2.poll必须轮询检测产生事件的文件描述符
    3.poll只能工作在水平触发模式(低速模式),无法工作在边沿触发(高速模式)

epoll

c 复制代码
epoll 
 int epoll_create(int size);
  功能:
    创建一张内核事件表
  参数:
    size:事件的个数
  返回值:
    成功返回文件描述符
    失败返回-1 

  epoll_ctl 
  int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
  功能:
    维护epoll时间表
  参数:
    epfd:事件表的文件描述符
    op:
        EPOLL_CTL_ADD   添加事件
        EPOLL_CTL_MOD   修改事件
        EPOLL_CTL_DEL   删除事件
    fd:
        操作的文件描述符
    event:
        事件对应的事件 
    
    typedef union epoll_data {
        void        *ptr;
        int          fd;
        uint32_t     u32;
        uint64_t     u64;
    } epoll_data_t;

    struct epoll_event {
        uint32_t     events;      /* Epoll events */
        epoll_data_t data;        /* User data variable */
    };

  返回值:
    成功返回0 
    失败返回-1 

  epoll_wait 
  int epoll_wait(int epfd, struct epoll_event *events,
                  int maxevents, int timeout);
  功能:
    监听事件表中的事件
  参数:
    epfd:文件描述符
    events:存放实际产生事件的数组空间首地址
    maxevents:最多存放事件的个数
    timeout:设定监听的时间(超过该时间则不再监听)
    -1 一直监听直到有事件发生
  返回值:
    成功返回产生事件的文件描述符个数
    失败返回-1 
    如果时间达到仍没有事件发生返回0 

eg:server.c文件

c 复制代码
#include"head.h"

int CreateListenSocket(char *pip,int port)
{
	int ret = 0;
	int sockfd = 0;
	struct sockaddr_in seraddr;

	sockfd = socket(AF_INET,SOCK_STREAM,0);
	if(-1 == sockfd)
	{
		perror("fail to socket");
		return -1;
	}

	seraddr.sin_family = AF_INET;
	seraddr.sin_port = htons(port);
	seraddr.sin_addr.s_addr = inet_addr(pip);
	ret = bind(sockfd,(struct sockaddr *)&seraddr,sizeof(seraddr));
	if(-1 == ret)
	{
		perror("fail to bind");
		return -1;
	}

	ret = listen(sockfd,10);
	if(-1 == ret)
	{
		perror("fail to listen");
		return -1;
	}

	return sockfd;
}

int HandleTcpClient(int confd)
{
	char tmpbuff[4096] = {0};
	ssize_t nsize = 0;

	memset(tmpbuff,0,sizeof(tmpbuff));
	nsize = recv(confd,tmpbuff,sizeof(tmpbuff),0);
	if (-1 == nsize)
	{
		perror("fail to recv");
		return -1;
	}
	else if (0 == nsize)
	{
		return 0;
	}

	sprintf(tmpbuff, "%s ----echo", tmpbuff);
	nsize = send(confd, tmpbuff, strlen(tmpbuff), 0);
	if (-1 == nsize)
	{
		perror("fail to send");
		return -1;
	}

	return nsize;
}

int main(void)
{
	int sockfd = 0;
	int confd = 0;
	int epfd = 0;
	struct epoll_event env;
	struct epoll_event retenv[1024];
	int nready = 0;
	int i = 0;
	int ret = 0;

	sockfd = CreateListenSocket("192.168.1.152",50000);

	epfd = epoll_create(1024);
	if(-1 == epfd)
	{
		return -1;
	}


	env.events = EPOLLIN;
	env.data.fd = sockfd;
	epoll_ctl(epfd,EPOLL_CTL_ADD,sockfd,&env);


	while(1)
	{
		nready = epoll_wait(epfd,retenv,1024,-1);
		if(-1 == nready)
		{
			perror("fail to epoll_wait");
			return -1;
		}

		for(i = 0;i < nready;++i)
		{
			if(retenv[i].data.fd == sockfd)
			{
				confd = accept(sockfd,NULL,NULL);
				if(-1 == confd)
				{
					perror("fail to accept");
					close(confd);
					env.events = EPOLLIN;
					env.data.fd = sockfd;
					epoll_ctl(epfd,EPOLL_CTL_DEL,sockfd,&env);
					continue;
				}
				env.events = EPOLLIN;
				env.data.fd = confd;
				epoll_ctl(epfd,EPOLL_CTL_ADD,confd,&env);
			}
			else if(retenv[i].data.fd != sockfd)
			{
				ret = HandleTcpClient(retenv[i].data.fd);
				if(-1 == ret)
				{
					fprintf(stderr,"handle tcp client failed!\n");
					close(retenv[i].data.fd);
					env.events = EPOLLIN;
					env.data.fd = retenv[i].data.fd;
					epoll_ctl(epfd,EPOLL_CTL_ADD,retenv[i].data.fd,&env);
					continue;
				}
				else if(0 == ret)
				{
					fprintf(stderr,"client disconnected!\n");
					close(retenv[i].data.fd);
					env.events = EPOLLIN;
					env.data.fd = retenv[i].data.fd;
					epoll_ctl(epfd,EPOLL_CTL_ADD,retenv[i].data.fd,&env);
					continue;
				}
			}

			
		}
	}
	close(confd);
	close(sockfd);

	return 0;
}

结果图也与上图类似

今天的内容就是这些,谢谢

相关推荐
元素之窗1 小时前
如何在 CentOS 中管理用户、组和服务状态
linux·运维·centos
咩咩大主教2 小时前
Linux下的简单TCP客户端和服务器
linux·服务器·c语言·开发语言·c++·tcp/ip·网络编程
DogDaoDao2 小时前
Cookie 介绍
计算机网络·计算机·浏览器·web·cookie·计算机安全
小理想!2 小时前
如何在Linux Centos7系统中挂载群晖共享文件夹
linux·运维·服务器
日晨难再2 小时前
Linux:终端(terminal)与终端管理器(agetty)
linux·运维·服务器
MAMA66812 小时前
一个简单的基于C语言的HTTP代理服务器的案例
c语言·网络·http
☆璇2 小时前
【数据结构】顺序表和链表经典题目
c语言·数据结构·链表
¥ 多多¥2 小时前
数据结构:单链表实现信息管理
c语言·数据结构·算法
z_鑫2 小时前
C语言:冒泡排序的注意事项及具体实现
c语言·开发语言·数据结构·算法
赵渝强老师3 小时前
【赵渝强老师】K8s的DaemonSets控制器
linux·docker·云原生·容器·kubernetes