管道
管道文件:进程间通信机制在文件系统的映射(linux下一切皆文件)。 管道分为有名管道(named pipe),和匿名管道,named表示在文件系统的路径。
使用linux命令创建管道文件:
$ mkfifo pipename.pipe #虽然linux不已后缀名区分文件类型,但通常用后缀来表示他是一个管道文件
管道按通信方式分:
- 单工通信:数据只能从A端到B端
- 半双工通信:A->B,也可B->A,但是不能同时发数据。
- 全双通信:A->B的同时,也可以B->A.
可以使用两根单工通信管道实现A<->B的即时通信。
打开管道文件
1A负责发消息,B负责接收消息
//A.c 写端
#include <sys/types.h>
#include <sys/stat.h>
#include <fcnt.h>
int main(int argc, int argv[]){
ARGS_CHECK(argc,2);
int fdw = open(argv[1],O_WRONLY);
ERROR_CHECK(fd, -1 , "open fdw");
write(fdw,"hello world!");
close(fdw);
return 0;
}
//B.c
//头文件略。
int main(int argc, char argv[]){
ARGS_CHECK(argc,2);
int fdr = open(argv[1],O_RDONLY);
ERROR_CHECK(fdr,-1,"open fdr");
char buf[1024];
read(fdr,buf,sizeof(buf));
printf("%s\n",buf);
close(fdr);
return 0;
}
🛰==注意:==对于以O_RDONLY/O_WRONLY打开的管道文件,若只打开一端会导致进程阻塞,直到
另一端也打开才会就绪。
读写管道的注意事项:
-
若读缓冲区没有数据,read函数会引发进程阻塞,知道进程有数据来往。
-
若写缓冲区有数据,write函数会导致写阻塞,直到接受端将数据取走。
管道的关闭注意事项:
-
写端先关闭,读端再调用read,会导致写一直就绪,但read()函数返回的是0;
-
读端先关闭,然后在调用write,会导致程序异常终止。
串行等待:阻塞式通信
服务器在处理多个客户端请求时,当被处理的客户端由于需要资源被动阻塞了,那么他同时会阻止服务器处理其他的请求,就算其他请求处于就绪状态也不行。
并行等待:非阻塞式通信
特点是:不会因为某一客户端因读写操作阻塞而影响其他客户端的通信。
IO多路复用机制之select:
模型大致分为四个步骤:
- 设置监听
- 用户等待(轮询算法)
- 直到任一监听的资源就绪,唤醒用户
- 找到就绪的资源,执行对应IO操作
selece函数是一种基于IO复用机制,它可以同时监听多个文件描述符的状态,一旦其中有任意一个文件描述符就绪(读写时间发生),就会返回给调用者,这种机制让多个进程处理多个网络链接的读写操作,提高并发性能。
select的函数声明如下:
int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, struct timeval* timeout);
//nfds:所有监听集合中fd的值最大的,再加1;
//readfds:读时间集合
//writefds:写时间集合
//exceptfds:异常时间集合
//timeout:超时时限
使用select函数实现A、B用户即时聊天程序:
yfg@yfg-Machine:~/cpp_50/linux10/study$ ls
1.pipe 2.pipe aqiang aqiang.c azhen azhen.c Makefile
#准备两根管道,实现即时通信。
//aqiang.c
#include <50func.h>
int main(int argc, char *argv[])
{
// ./4_aqiang_select 1.pipe 2.pipe
ARGS_CHECK(argc,3);
int fdr = open(argv[1],O_RDONLY);
ERROR_CHECK(fdr,-1,"open fdr");
int fdw = open(argv[2],O_WRONLY);
ERROR_CHECK(fdw,-1,"open fdw");
printf("aqiang is OK\n");
char buf[4096];
fd_set rdset;//读集合
while(1){
FD_ZERO(&rdset); //初始化监听集合
FD_SET(STDIN_FILENO,&rdset);//将标准输入加入监听
FD_SET(fdr,&rdset);//将管道的读端加入监听
select(fdr+1,&rdset,NULL,NULL,NULL);
//最大数值的fd 是 fdr
//读监听集合 rdset
//写监听集合 异常集合没有
//永久等待
//
//从select返回以后,rdset是就绪集合
//遍历就绪集合
ssize_t sret;
if(FD_ISSET(STDIN_FILENO,&rdset)){//从标准输入中获取数据
memset(buf,0,sizeof(buf));
sret = read(STDIN_FILENO,buf,sizeof(buf));
if(sret == 0){ //主动退出
write(fdw,"nishigehaoren",13);
break;
}
time_t now = time(NULL);
char *p = ctime(&now);
write(fdw,p,strlen(p));
write(fdw,buf,strlen(buf));
}
if(FD_ISSET(fdr,&rdset)){
memset(buf,0,sizeof(buf));
sret = read(fdr,buf,sizeof(buf));
if(sret == 0){//对方已经终止了
printf("Hehe\n");
break;
}
printf("buf = %s\n", buf);
}
}
close(fdr);
close(fdw);
return 0;
}
//azhen.c
#include <50func.h>
int main(int argc, char *argv[])
{
// ./4_azhen_select 1.pipe 2.pipe
ARGS_CHECK(argc,3);
int fdw = open(argv[1],O_WRONLY);
ERROR_CHECK(fdw,-1,"open fdw");
int fdr = open(argv[2],O_RDONLY);
ERROR_CHECK(fdr,-1,"open fdr");
printf("azhen is OK\n");
char buf[4096];
fd_set rdset;//读集合
time_t lastactive = time(NULL);
while(1){
FD_ZERO(&rdset); //初始化监听集合
FD_SET(STDIN_FILENO,&rdset);//将标准输入加入监听
FD_SET(fdr,&rdset);//将管道的读端加入监听
struct timeval timeout;
timeout.tv_sec = 1;
timeout.tv_usec = 0;
select(fdr+1,&rdset,NULL,NULL,&timeout);
time_t curtime = time(NULL);
printf("curtime = %s\n", ctime(&curtime));
ssize_t sret;
if(FD_ISSET(STDIN_FILENO,&rdset)){//从标准输入中获取数据
memset(buf,0,sizeof(buf));
sret = read(STDIN_FILENO,buf,sizeof(buf));
if(sret == 0){ //主动退出
write(fdw,"nishigehaoren",13);
break;
}
time_t now = time(NULL);
char *p = ctime(&now);
write(fdw,p,strlen(p));
write(fdw,buf,strlen(buf));
}
if(FD_ISSET(fdr,&rdset)){
lastactive = time(NULL); //更新阿强的活跃时间
memset(buf,0,sizeof(buf));
sret = read(fdr,buf,sizeof(buf));
if(sret == 0){//对方已经终止了
printf("Hehe\n");
break;
}
printf("buf = %s\n", buf);
}
if(curtime - lastactive > 10){
write(fdw,"byebye",6);
break;
}
}
close(fdr);
close(fdw);
return 0;
}