目录
迭代服务器(socket+bind+listen)

特点:
- 实现简单;
- 可以处理多个客户端,但是只能是串行的
并发服务器(socket+bind+listen)

多进程存在的问题:
- 资源开销大(创建的开销、调度的开销比较大);
- 进程的退出(僵尸态资源的回收)
多线程相比于进程:
- 线程的创建与调度开销小
- 线程共享资源方便
- 线程之间的竞争还有同步的问题
IO多路复用

核心目的:提高并发的程度
多路IO ------ I(input)O(output) 当前进程有多处输入和输出的操作

复用 ------
阻塞IO

scanf、getchar、fgets
阻塞IO
读取时
写入时(管道)
特点
- 当读取数据时,内核没有数据,这种读取操作会一直阻塞,直到内核有数据
- 实现简单,但是一直等效率不是很高
非阻塞IO

特点
- 当去内核读取数据时,如果内核没有数据则不阻塞等待而是立即返回,所以非阻塞方式如果想要获得数据必须配合轮询操作
- 如果是非阻塞,必须要while(1),很耗cpu


cpp
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
int main(int argc, const char *argv[])
{
if (argc != 2)
{
printf("Usage: %s <fifo file>\n",argv[0]);
return -1;
}
if (mkfifo(argv[1],0666) < 0 && errno!= EEXIST)
{
perror("mkfifo fail");
return -1;
}
printf("success\n");
//1.打开文件
//int fd = open(argv[1],O_RDONLY);
int fd = open(argv[1],O_RDONLY|O_NONBLOCK);
if (fd < 0)
{
perror("open fail");
return -1;
}
#if 0
//将fd设置为非阻塞
int flag = fcntl(fd,F_GETFL,0);
flag = flag | O_NONBLOCK;
fcntl(fd,F_SETFL,flag);
#endif
printf("read -------\n");
while (1)
{
//2.写
char buf[1024] = {0};
int ret = read(fd,buf,sizeof(buf));
perror("read test");
printf("ret :%d buf = %s\n",ret,buf);
if (strncmp(buf,"quit",4) == 0)
{
break;
}
sleep(1);
}
//3.关闭
close(fd);
if (unlink(argv[1]) < 0)
{
perror("unlink fail");
return -1;
}
return 0;
}
信号驱动IO
- 设置好对SIGIO的信号处理函数,等有数据来,内核会给进程发信号,进程收到信号后再去做读取操作
- 不需要一直等,也不需要轮询

使用流程

- 为内核发送的通知信号安装一个信号处理例程,默认情况下这个通知信号为SIGIO
- 设定文件描述符的属主,也就是当文件描述符上可执行I/O时会接收到通知信号的进程或进程组。通常我们让调用进程称为属主,设定属主可通过fcntl()的F_SETOWN操作来完成
- 设置标志,通过设定O_NONBLOCK标志能使非阻塞IO,通过打开O_ASYNC标志能使信号驱动IO
特点
异步的,效率高,但是存在的问题是只能处理一路

cpp
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <stdlib.h>
int fd;
void do_handler(int signo)
{
char buf[1024]={0};
int ret = read(fd,buf,sizeof(buf));
perror("read test");
printf("ret :%d buf :%s\n",ret,buf);
if(strncmp(buf,"quit",4)==0)
{
exit(0);
}
}
int main(int argc, const char *argv[])
{
if (argc != 2)
{
printf("Usage: %s <fifo file>\n",argv[0]);
return -1;
}
if (mkfifo(argv[1],0666) < 0 && errno!= EEXIST)
{
perror("mkfifo fail");
return -1;
}
printf("success\n");
//1.设置信号处理函数
signal(SIGIO,do_handler);
//1.打开文件
//int fd = open(argv[1],O_RDONLY);
int fd = open(argv[1],O_RDONLY|O_NONBLOCK);
if (fd < 0)
{
perror("open fail");
return -1;
}
//设置fd所属者
fcntl(fd,F_SETOWN,getpid());
#if 0
//将fd设置为非阻塞
int flag = fcntl(fd,F_GETFL,0);
flag = flag | O_NONBLOCK;
fcntl(fd,F_SETFL,flag);
#endif
printf("read -------\n");
while (1)
{
//2.写
char buf[1024] = {0};
int ret = read(fd,buf,sizeof(buf));
perror("read test");
printf("ret :%d buf = %s\n",ret,buf);
if (strncmp(buf,"quit",4) == 0)
{
break;
}
sleep(1);
}
//3.关闭
close(fd);
if (unlink(argv[1]) < 0)
{
perror("unlink fail");
return -1;
}
return 0;
}
IO多路复用

IO多路复用流程
- 用函数(select/ epoll(poll))监控多路IO
- 如果哪一路IO有消息,则对应的函数返回告诉用户进程有就绪IO
- 则用户进程去对应的IO读取数据
select函数

#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>int select( int nfds,
fd_set *readfds,
fd_set *writefds,
fd_set *exceptfds,struct timeval *timeout
);
void FD_CLR(int fd, fd_set *set); 从set中清除fd
int FD_ISSET(int fd, fd_set *set); 判断fd是否在set中
void FD_SET(int fd, fd_set *set); 将fd添加到set中
void FD_ZERO(fd_set *set); 将set设置为0(清零)
功能
- 监控多路IO,看是否有就绪的
参数
- nfds:要监控的文件描述符 最大值+1(也就是下面三个的最大值+1)
- readfds:看是否可读的集合(最常用)
- writefds:看是否可写的集合
- exceptfds:看是否异常的集合
- timeout:0(时间为0表示select是一个非阻塞调用)/ >0(时间大于0表示select阻塞对应的一段时间)/NULL(表示默认使用阻塞方式)
返回值
- 成功返回 就绪的文件描述符的数量
- 失败返回 -1而且错误码被设置
select使用流程

(1)准备监控的文件描述符表
fd_set readfds;
FD_ZERO(&readfds);

(2)添加要监控的文件描述符
FD_SET(fd,&readfds);
FD_SET(0,&readfds);
(3)准备nfds
nfds = fd+1;
(4)调用select函数
select(nfds,&readfds,NULL,NULL,NULL);
注意

(1)select函数调用完之后会将调用监控结果的文件描述符的集合带回给readfds,select监控到就绪的文件描述符后,会将未就绪的清零。
cpp
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <stdlib.h>
int fd;
void do_handler(int signo)
{
char buf[1024]={0};
int ret = read(fd,buf,sizeof(buf));
perror("read test");
printf("ret :%d buf :%s\n",ret,buf);
if(strncmp(buf,"quit",4)==0)
{
exit(0);
}
}
int main(int argc, const char *argv[])
{
if (argc != 2)
{
printf("Usage: %s <fifo file>\n",argv[0]);
return -1;
}
if (mkfifo(argv[1],0666) < 0 && errno!= EEXIST)
{
perror("mkfifo fail");
return -1;
}
printf("success\n");
//1.设置信号处理函数
//signal(SIGIO,do_handler);
//1.打开文件
//int fd = open(argv[1],O_RDONLY);
int fd = open(argv[1],O_RDONLY|O_NONBLOCK);
if (fd < 0)
{
perror("open fail");
return -1;
}
//设置fd所属者
//fcntl(fd,F_SETOWN,getpid());
#if 0
//将fd设置为非阻塞
int flag = fcntl(fd,F_GETFL,0);
flag = flag | O_NONBLOCK;
fcntl(fd,F_SETFL,flag);
#endif
printf("read -------\n");
//select使用流程
//1.准备监控文件描述符
fd_set readfds;
FD_ZERO(&readfds);
//2.添加要监控的文件描述符
FD_SET(fd,&readfds);
FD_SET(0,&readfds);
//3.准备nfds
int nfds = fd + 1;
fd_set backfds;
while (1)
{
backfds = readfds;
//4.调用select函数
int ret = select(nfds,&backfds,NULL,NULL,NULL);
if(ret < 0)
{
perror("select fail");
return -1;
}
printf("ret :%d\n",ret);
if(ret > 0)
{
int i=0;
for(i=0;i<nfds;i++)
{
if(FD_ISSET(i,&backfds))
{
if(i== fd)
{
//2.写
char buf[1024] = {0};
int ret = read(fd,buf,sizeof(buf));
printf("ret :%d buf = %s\n",ret,buf);
if (strncmp(buf,"quit",4) == 0)
{
break;
}
}else if(i == 0)
{
if(FD_ISSET(i,&backfds))
{
char buf[1024]={0};
int ret = read(0,buf,sizeof(buf));
printf("stdin ret:%d buf:%s\n",ret,buf);
if(strncmp(buf,"quit",4)==0)
{
break;
}
}
}
}
}
}
}
//3.关闭
close(fd);
if (unlink(argv[1]) < 0)
{
perror("unlink fail");
return -1;
}
return 0;
}
(2)select监控超时,select函数每次会将timeout中的时间往下减,减法的结果放到timeout中,使用时,timeout中的值每次调用前,需要重新给值
cpp
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <stdlib.h>
int fd;
void do_handler(int signo)
{
char buf[1024]={0};
int ret = read(fd,buf,sizeof(buf));
perror("read test");
printf("ret :%d buf :%s\n",ret,buf);
if(strncmp(buf,"quit",4)==0)
{
exit(0);
}
}
int main(int argc, const char *argv[])
{
if (argc != 2)
{
printf("Usage: %s <fifo file>\n",argv[0]);
return -1;
}
if (mkfifo(argv[1],0666) < 0 && errno!= EEXIST)
{
perror("mkfifo fail");
return -1;
}
printf("success\n");
//1.设置信号处理函数
//signal(SIGIO,do_handler);
//1.打开文件
//int fd = open(argv[1],O_RDONLY);
int fd = open(argv[1],O_RDONLY|O_NONBLOCK);
if (fd < 0)
{
perror("open fail");
return -1;
}
//设置fd所属者
//fcntl(fd,F_SETOWN,getpid());
#if 0
//将fd设置为非阻塞
int flag = fcntl(fd,F_GETFL,0);
flag = flag | O_NONBLOCK;
fcntl(fd,F_SETFL,flag);
#endif
printf("read -------\n");
//select使用流程
//1.准备监控文件描述符
fd_set readfds;
FD_ZERO(&readfds);
//2.添加要监控的文件描述符
FD_SET(fd,&readfds);
FD_SET(0,&readfds);
//3.准备nfds
int nfds = fd + 1;
fd_set backfds;
while (1)
{
//注意:每次调用前需要重新给值
struct timeval tv = {3,0};
backfds = readfds;
//4.调用select函数
int ret = select(nfds,&backfds,NULL,NULL,NULL);
if(ret < 0)
{
perror("select fail");
return -1;
}
printf("ret :%d\n",ret);
if(ret > 0)
{
int i=0;
for(i=0;i<nfds;i++)
{
if(FD_ISSET(i,&backfds))
{
if(i== fd)
{
//2.写
char buf[1024] = {0};
int ret = read(fd,buf,sizeof(buf));
printf("ret :%d buf = %s\n",ret,buf);
if (strncmp(buf,"quit",4) == 0)
{
break;
}
}else if(i == 0)
{
if(FD_ISSET(i,&backfds))
{
char buf[1024]={0};
int ret = read(0,buf,sizeof(buf));
printf("stdin ret:%d buf:%s\n",ret,buf);
if(strncmp(buf,"quit",4)==0)
{
break;
}
}
}
}
}
}
}
//3.关闭
close(fd);
if (unlink(argv[1]) < 0)
{
perror("unlink fail");
return -1;
}
return 0;
}
select存在的问题

select实现服务器并发
1.listenfd=socket
2.bind
3.listen
fd_set readfds;
//清空
FD_ZERO(&readfds);
//listenfd
FD_SET(listenfd,readfds);
//1.nfds --> 最大的文件描述符+1
int nfds = listenfd + 1; //注意: 是三个集合中最大的文件描述符加1
int ret = 0;
int i = 0;
fd_set bakfds = readfds;
while (1)
{
bakfds = readfds;
ret = select(nfds,&bakfds,NULL,NULL,NULL);
if (ret > 0)
{ //表示有就绪的文件描述符
//寻找具体是哪个文件描述符就绪
for (i = 0; i < nfds; i++)
{
if (FD_ISSET(i,&bakfds))
{ //只能判断出 i 是否在bakfds中
if (i == listenfd) //意味着有客户端发起连接请求
{
connfd = accpet();
//实现并发,添加对新连接的套接字的监控
//1. 添加到要监控的表中,进行监控
FD_SET(connfd,&readfds);
if (connfd + 1> nfds)
nfds = connfd+1;
}
else //创建 --- 子进程 或 线程
{ //此时肯定是某个客户端对应的connfd的值
read(i,buf,sizeof(buf)); //从i中读值,
//因为i表示的是当前就绪的文件描述符
printf("buf == %s\n",buf);
}
}
}
}
}
cpp
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <arpa/inet.h>
#include <strings.h>
#include <string.h>
#include <unistd.h>
int server_init(const char *ip,unsigned short port)
{
//1. socket
int fd = socket(AF_INET,SOCK_STREAM,0);
if (fd < 0)
{
perror("socket fail");
return -1;
}
//2. bind
struct sockaddr_in addr; //结构体
bzero(&addr,sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
addr.sin_port = htons(50000);
if (bind(fd,(const struct sockaddr*)&addr,sizeof(addr)) < 0)
{
perror("bind fail");
return -1;
}
//3.listen
if (listen(fd,5) < 0)
{
perror("listen fail");
return -1;
}
return fd;
}
//tcp 客户端
int main(int argc, const char *argv[])
{
//1.链接
int listenfd = server_init("127.0.0.1",50000);
if (listenfd < 0)
{
printf("server_init fail\n");
return -1;
}
//2.通信
struct sockaddr_in cliaddr;
bzero(&cliaddr,sizeof(cliaddr));
socklen_t len = sizeof(cliaddr);
//1.准备fd_set
fd_set readfds;
FD_ZERO(&readfds);
//2.添加要监控的文件描述符
FD_SET(listenfd,&readfds);
//3.准备nfds
int nfds = listenfd + 1;
fd_set backfds;
while (1)
{
backfds = readfds;
int ret = select(nfds,&backfds,NULL,NULL,NULL);
if (ret < 0)
{
perror("select fail");
return -1;
}
int i = 0;
for (i = 0; i < nfds; ++i)
{
if (FD_ISSET(i,&backfds))
{
if (i == listenfd)
{
//4.accept
int connfd = accept(listenfd,(struct sockaddr*)&cliaddr,&len);
if (connfd < 0)
{
perror("accept fail");
return -1;
}
printf("connfd = %d\n",connfd);
printf("--------------------\n");
printf("client ip = %s\n",inet_ntoa(cliaddr.sin_addr));
printf("client port = %d\n",ntohs(cliaddr.sin_port));
//添加connfd 到 readfds
FD_SET(connfd,&readfds);
if (nfds < connfd+1)
{
nfds = connfd + 1;
}
}else
{
char buf[1024];
read(i,buf,sizeof(buf));
printf("ser buf = %s\n",buf);
if(strncmp(buf,"quit",4)==0)
{
//客户端退出时,关闭i和从readfds清除
close(i);
FD_CLR(i,&readfds);
}
char sbuf[1024];
sprintf(sbuf,"server + %s\n",buf);
write(i,sbuf,strlen(sbuf) + 1);
}
}
}
}
return 0;
}

epoll函数

1、创建监控的epoll对象

epoll_create
①int epoll_create(int size);
功能
- 创建epoll对象,返回文件描述符
参数

返回值
- 成功返回 epoll描述符
- 失败返回 -1表示创建失败
epoll_ctl函数

功能
- epoll对象的控制函数
参数

返回值
- 成功返回 0表示控制成功
- 失败返回 -1表示失败
epoll_wait

功能
- 监控文件描述符是否就绪
参数
- epfd:表示监控的epoll对象
- events:表示获得就绪事件的一个结构体指针(理解为一个struct epoll_event类型的数组名)
- maxevents:表示该数组的最大容量
- timeout:表示超时的时间,单位:毫秒,如果是0表示非阻塞(等待0ms),如果是-1表示阻塞,我们也可以给一个非负数表示阻塞多少微秒
返回值
- 成功返回 就绪的文件描述符的数量,如果设置了超时,超时时间内没有就绪的,则返回0
- 失败返回 -1而且错误码会被设置


