今天主要学习了TCP并发模型,并且理解了TCP多线程模型的缺点,最主要的就是:创建线程会带来资源开销,能够实现的并发量比较有限
IO模型
IO模型主要分为4类,分别是:1.阻塞IO:没有数据到来时,可以让任务挂起,节省CPU资源开销,提高系统效率2.非阻塞IO:程序未接收到数据时一直执行,效率很低3.异步IO:只能绑定一个文件描述符用来读取数据4.多路复用IO:select1.select监听的集合中的文件描述符有上限限制2.select有内核层向用户层数据空间拷贝的过程,占用系统资源开销3.select必须轮询检测产生事件的文件描述符4.select只能工作在水平触发模式(低速模式),无法工作在边沿触发(高速模式)poll1.poll有内核层向用户层数据空间拷贝的过程,占用系统资源开销2.poll必须轮询检测产生事件的文件描述符3.poll只能工作在水平触发模式(低速模式),无法工作在边沿触发(高速模式)epoll :这个明天来讲
主要介绍一下关于TCP多路复用IO
在多路复用中,其client.c一样,所以在这里把代码写一下,在下面只写客户端的代码
client.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:协议,默认为0if (-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
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从集合中清除 intFD_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代码
#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
pollint 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
#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类似,但是缺点少了一点:poll1.poll有内核层向用户层数据空间拷贝的过程,占用系统资源开销2.poll必须轮询检测产生事件的文件描述符3.poll只能工作在水平触发模式(低速模式),无法工作在边沿触发(高速模式)
epoll
epollint 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;intfd;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文件
#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;}
结果图也与上图类似
今天的内容就是这些,谢谢