今天主要学习了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;
}
结果图也与上图类似
今天的内容就是这些,谢谢