《UNIX网络编程 卷2》读书笔记(一)

本文详细介绍了进程间通信(IPC)的各种机制,包括POSIX IPC的名字获取方法、消息队列、信号量和共享内存的使用,以及System V IPC的键值概念。此外还探讨了管道和有名管道(FIFO)的工作原理,并提供了具体的代码示例。
1,获取Posix IPC的名字
#include "unpipc.h"

char* px_ipc_name(const char* name)
{
      char* dir,*dst,*slash;
      if((dst = malloc(PATH_MAX))==NULL) return NULL;//分配失败
      if((dir=getenv("PX_IPC_NAME"))==NULL)
      {//目录名
          #ifdef POSIX_IPC_PREFIX
               dir = POSIX_IPC_PREFIX;
          #else
               dir = "/tmp/";
          #endif
      }
      slash = (dir[strlen(dir)-1] == '/')?"":"/";
      snprintf(dst,PATH_MAX,"%S%S%S",dir,slash,name);//全路径名称
      return dst;
}

2,Mq_open,lsem_open,shm_open用来创建或打开一个IPC对象,第2个参数oflag指定打开IPC对象的方式。消息队列可以以只读,只写或读写任何一种模式打开,信号灯的打开不指定任何模式,共享内存区不能以只写模式打开
3,System V IPC使用key_t作为其名字,通常是先调用stat函数,再调用ftok函数得到的,函数ftok把一个已经存在的路径和一个整数标识符转换为一个key_t值,叫IPC键(IPC key).
4,内核给每个IPC对象维护一个数据结构
struct ipc_perm
{
   uid_t uid;//owner的用户ID 
   gid_t gid;//owner的组ID 
   uid_t cuid;//creater的用户ID
   gid_t cgid;//creater的组ID
   mode_t mode;//读写模式 
   ulong_t seq;//序列号 
   key_t key;//IPC key 
};

   ipc_perm 结构体中的seq成员很有意思,它是作为一个计数器,每当删除一个IPC时,内核就递增这个变量,若溢出则循环回到0.
 
5,管道是最初的Unix IPC形式,其局限性在于没有名字,而且只能由有亲缘关系的进程使用。FIFO又叫有名管道,任何进程之间都能使用。两者都可以使用通常的read和write访问。
6,
#include "unpipc.h"
void client(int readfd,int writefd)
{//客户 
     size_t len;
     ssize_t n;
     char buff[MAXLINE];
     fgets(buff,MAXLINE,stdin);//读入路径名
     len = strlen(buff);
     if(buff[len-1]=='\n')//去掉结尾处的行符 
        len--;
     write(writefd,buff,len);//写入管道  
     while((n==read(readfd,buff,MAXLINE))>0)//从管道读入数据 
         write(STDOU_FILENO,buff,n);//输出 
      
void server(int readfd,int writefd)
{//服务器 
     int fd;
     ssize_t n;
     char buff[MAXLINE+1];

     if((n==read(readfd,buff,MAXLINE))>0)//从管道读入数据 
         buff[n]='\0';
     if((fd=open(buff,0_RDONLY))<0)
     {//给客户端返回出错信息 
         snprintf(buff+n,sizeof(buff)-n,"can't open,%s\n",strerror(errno));
         n = strlen(buff);
         write(writefd,buff,n);
     }
     else
     {
          while((n=read(fd,huff,MAXLINE))>0)
          {//给客户端返回文件内容 
              write(writefd,buff,n);
          }
          close(fd);
     }    
}

int main(int argc,char** argv)
{
    int pipe1[2],pipe2[2];
    pid_t childpid;
    //创建两个管道 
    pipe(pipe1);
    pipe(pipe2);
    if((childpid=fork())==0)
    {
        close(pipe1[1]);//子进程关闭管道1的写入端 
        close(pipe2[0]);//子进程关闭管道2的读出端 
        server(pipe1[0],pipe2[1]);
        exit(0);
    }
    close(pipe1[0]);//父进程关闭管道1的读出端 
    close(pipe2[1]);//父进程关闭管道2的写入端 
    client(pipe2[0],pipe1[1]);
    waitpid(childpid,NULL,0);
    return 0;
}



上面这个例子也可以使用popen和cat来实现
#include "unpipc.h"

int main(int argc,char** argv)
{
    size_t n;
    char buff[MAXLINE],command[MAXLINE];
    FILE* fp;
    fgets(buff,MAXLINE,stdin);//读入路径名
    n = strlen(buff);
    if(buff[n-1]=='\n')n--;
    snprintf(command,sizeof(command),"cat %s",buff);//构造命令字
    fp = popen(command,"r");//创建管道并启动另一个进程
    while(fgets(buff,MAXLINE,fp)!=NULL)
        fputs(buff,stdout);
    pclose(fp);
    return 0;
}

7,FIFO是一个单向数据流,有一个路径名与之关联,从而允许没有亲缘关系的进程访问同一个FIFO,也叫有名管道(named pipe),由函数mkfifo创建。
#include "unpipc.h"
#define FIFO1 "tmp/fifo.1"
#define FIFO2 "tmp/fifo.2"

void server(int readfd,int writefd)
{//服务器 
     int fd;
     ssize_t n;
     char buff[MAXLINE+1];

     if((n==read(readfd,buff,MAXLINE))>0)//从管道读入数据 
         buff[n]='\0';
     if((fd=open(buff,0_RDONLY))<0)
     {//给客户端返回出错信息 
         snprintf(buff+n,sizeof(buff)-n,"can't open,%s\n",strerror(errno));
         n = strlen(buff);
         write(writefd,buff,n);
     }
     else
     {
          while((n=read(fd,huff,MAXLINE))>0)
          {//给客户端返回文件内容 
              write(writefd,buff,n);
          }
          close(fd);
     }    
}
int main(int argc,char** argv)
{
    int readfd,writefd;
    pid_t childpid;
    //创建两个有名管道 
    mkfifo(FIFO1,FILE_MODE);
    mkfifo(FIFO2,FILE_MODE);

    readfd = open(FIFO1,O_RDONLY,0);
    writefd = open(FIFO2,O_WRONLY,0);
    server(readfd,writefd);
    return 0;
}

void client(int readfd,int writefd)
{//客户 
     size_t len;
     ssize_t n;
     char buff[MAXLINE];
     fgets(buff,MAXLINE,stdin);//读入路径名
     len = strlen(buff);
     if(buff[len-1]=='\n')//去掉结尾处的行符 
        len--;
     write(writefd,buff,len);//写入管道  
     while((n==read(readfd,buff,MAXLINE))>0)//从管道读入数据 
         write(STDOU_FILENO,buff,n);//输出 
      

int main(int argc,char** argv)
{
    int readfd,writefd;
    pid_t childpid;
    //创建两个有名管道 
    mkfifo(FIFO1,FILE_MODE);
    mkfifo(FIFO2,FILE_MODE);

    readfd = open(FIFO1,O_RDONLY,0);
    writefd = open(FIFO2,O_WRONLY,0);
    server(readfd,writefd);
    
    writefd = open(FIFO1,O_WRONLY,0);
    readfd = open(FIFO2,O_RDONLY,0);
    client(readfd,writefd);

    close(readfd);
    close(writefd);
    //删除有名管道 
    unlink(FIFO1);
    unlink(FIFO2);
    return 0;
}

8,能通过两种方式设置为非阻塞:
1)调用open时指定O_NONBLOCK标志。
2)若描述字已经打开,用fcntl来enable掉O_NONBLOCK标志,对于管道来说,必须这样,因为它没有open调用,在pipe调用中也无法指定O_NONBLOCK标志,代码如下:
int flags;
flags = fcntl(fd,F_GETFL,0);
flags |= O_NONBLOCK;
fcntl(fd,F_SETFL,flags);
9,单进程服务器,多客户端
服务器代码:
int main(int argc,char** argv)
{
    int readfifo,writefifo,dummyfd,fd;
    char *ptr,buff[MAXLINE],fifoname[MAXLINE];
    pid_t pid;
    ssize_t n;
    mkfifo(SERV_FIFO,FILE_MODE);
    readfifo = open(SERV_FIFO,O_RDONLY,0);//服务器的读管道 
    dummyfd = open(SERV_FIFO,O_WRONLY,0);//写管道,没使用,开启为了防止服务器管道重复打开,关闭 
    while((n=readline(readfifo,buff,MAXLINE))>0)
    {
        if(buff[n-1]=='\n')n--;
        buff[n] = '\0';
        if((ptr=strchr(buff,''))==NULL)
        {
            err_msg("请求命令错误");
            continue; 
        }
        *ptr++ = 0;
        pid = atoi(buff);//获取客户端进程号 
        snprintf(fifoname,sizeof(fifoname),"/tmp/fifo.%ld",(long)pid);
        writefifo = open(fifoname,O_WRONLY,0);//打开客户端管道的写端 
        if(fd = open(ptr,O_RDONLY,0))<0)
        {//读文件失败 
           snprintf(buff+n,sizeof(buff)-n,"can't open,%s\n",strerror(errno));
           n = strlen(ptr);
           write(writefifo,ptr,n);//往客户端管道写数据 
           close(writefifo);
        }
        else
        {
            while((n = read(fd,buff,MAXLINE))>0)
            {
                write(writefifo,buff,n);
            }
            close(fd);
            close(writefifo);
        }
    }
    return 0;
}

客户端代码:
int main(int argc,char** argv)
{
    int readfifo,writefifo;
    size_t len;
    ssize_t n;
    char *ptr,buff[MAXLINE],fifoname[MAXLINE];
    pid_t pid;
    pid = getpid();//获取当前进程ID 
    snprintf(fifoname,sizeof(fifoname),"/tmp/fifo.%ld",(long)pid);
    
    mkfifo(fifoname,FILE_MODE);//创建客户端管道
    snprintf(buff,sizeof(buff),"%ld",(long)pid);
    len  = strlen(buff);
    ptr = buff+len;
    //读入文件路径名 
    fgets(ptr, MAXLINE-len,stdin);
    len = strlen(buff);
    writefifo = open(SERV_FIFO,O_WRONLY,0);//打开服务器的写管道 \
    write(writefifo,buff,len);//给服务器传送指定格式的命令字
    readfifo = open(fifoname,O_RDONLY,0);/打开客户端读管道
   
    while((n=read(readfifo,buff,MAXLINE))>0)
    {
        write(STDOUT_FILENO,buff,n);//输出 
    }
    close(readfifo);//关闭读端
    unlink(fifoname);//删除管道 
    return 0;
}

但是这个程序会导致DoS攻击,因为服务器会一直阻塞在对客户端FIFO的open调用中,若客户端不打开此FIFO来读,则服务器会一直阻塞,因此客户很容易通过阻止发送封包来使得服务器垮掉,解决办法有很多,可以一个客户一个进程,或一个客户一个线程,或使用进程池,或线程池。

10,UNIX默认的I/O模型是字节流模型,也就是不存在记录边界,一般有3种技巧来改造:
1)带内特殊终止序列:很多使用换行符来分隔每个消息,写入进程给每个消息加入一个换行符,读出进程每次读出一行,但缺点是分隔符要进行转义处理。如:FTP,SMTP等就使用一个回车后跟一个换行符来分隔记录。
2)显示长度:记录前加入其长度。
3)一次连接一个记录,通过关闭与对方的连接来指示一个记录结束,例如HTTP1.0
#include "unpipc.h"

#define MAXMESGDATA (PIPE_BUF-2*sizeof(long))
#define MESGHDRSIZE (sizeof(struct mymesg)-MAXMESGDATA)

struct mymesg
{
   long mesg_len;//消息长度 
   long mesg_type;//消息类型 
   long mesg_data[MAXMESGDATA];//数据域 
};

size_t mesg_send(int fd,struct mymesg* mptr)
{
   return(write(fd,mptr,MESGHDRSIZE+mptr->mesg_len));
}

ssize_t mesg_recv(int fd,struct mymesg* mptr)
{
   size_t len;
   ssize_t n;
   if((n = read(fd,mptr,MESGHDRSIZE))==0)
   {
       return 0;
   }
   else if(n!= MESGHDRSIZE)
      err_quit("头部错误");
   if((len = mptr->mesg_len)>0)
   {
      if((n=read(fd,mptr->mesg_data,len))!=len)
         err_quit("数据错误"); 
   } 
   return len;
   
}
void client(int readfd,int writefd)
{//客户 
     size_t len;
     ssize_t n;
     struct mymesg mesg;
     
     char buff[MAXLINE];
     fgets(mesg.mesg_data,MAXMESGDATA,stdin);//读入路径名
     len = strlen(mesg.mesg_data);
     if(mesg.mesg_data[len-1]=='\n')//去掉结尾处的行符 
        len--;
     mesg.mesg_len = len;
     mesg.mesg_type = 1;
     mesg_send(writefd,&mesg);
     while((n==mesg_recv(readfd,&mesg))>0)//从管道读入数据 
         write(STDOU_FILENO,mesg.mesg_data,n);//输出 
      
void server(int readfd,int writefd)
{//服务器 
     int fd;
     ssize_t n;
     char buff[MAXLINE+1];
     FIFL *fp;
     struct mymesg mesg;
     mesg.mesg_type = 1;
     

     if((n==mesg_recv(readfd,&mesg))>0)//从管道读入数据 
         mesg.mesg_data[n]='\0';
     if((fd=fopen(mesg.mesg_data,"r"))==NULL)
     {//给客户端返回出错信息 
         snprintf( mesg.mesg_data+n,sizeof( mesg.mesg_data)-n,"can't open,%s\n",strerror(errno));
          mesg.mesg_len = strlen( mesg.mesg_data);
         mesg_send(writefd, &mesg);
     }
     else
     {
          while(fgets( mesg.mesg_data,MAXMESGDATA,fp))!=NULL)
          {//给客户端返回文件内容 
              mesg.mesg_len = strlen(mesg.mesg_data);
              mesg_send(writefd,&mesg);
          }
          fclose(fp);
     }  
     mesg.mesg_len = 0 ;
     mesg_send(writefd,&mesg);  
}




本文转自Phinecos(洞庭散人)博客园博客,原文链接:http://www.cnblogs.com/phinecos/archive/2008/05/26/1207943.html,如需转载请自行联系原作者
部分 简介   第1章 简介 2   1.1 概述 2   1.2 进程、线程信息共享 3   1.3 IPC对象的持续性 4   1.4 名字空间 5   1.5 fork、exec和exit对IPC对象的影响 7   1.6 出错处理:包裹函数 8   1.7 Unix标准 9   1.8 书中IPC例子索引表 11   1.9 小结 13   习题 13   第2章 Posix IPC 14   2.1 概述 14   2.2 IPC名字 14   2.3 创建打开IPC通道 16   2.4 IPC权限 18   2.5 小结 19   习题 19   第3章 System V IPC 20   .3.1 概述 20   3.2 key_t键和ftok函数 20   3.3 ipc_perm结构 22   3.4 创建打开IPC通道 22   3.5 IPC权限 24   3.6 标识符重用 25   3.7 ipcs和ipcrm程序 27   3.8 内核限制 27   3.9 小结 28   习题 29   第二部分 消息传递   第4章 管道和FIFO 32   4.1 概述 32   4.2 个简单的客户-服务器例子 32   4.3 管道 32   4.4 全双工管道 37   4.5 popen和pclose函数 39   4.6 FIFO 40   4.7 管道和FIFO的额外属性 44   4.8 单个服务器,多个客户 46   4.9 对比迭代服务器并发服务器 50   4.10 字节流消息 51   4.11 管道和FIFO限制 55   4.12 小结 56   习题 57   第5章 Posix消息队列 58   5.1 概述 58   5.2 mq_open、mq_close和mq_unlink函数 59   5.3 mq_getattr和mq_setattr函数 61   5.4 mq_send和mq_receive函数 64   5.5 消息队列限制 67   5.6 mq_notify函数 68   5.7 Posix实时信号 78   5.8 使用内存映射I/O实现Posix消息队列 85   5.9 小结 101   习题 101   第6章 System V消息队列 103   6.1 概述 103   6.2 msgget函数 104   6.3 msgsnd函数 104   6.4 msgrcv函数 105   6.5 msgctl函数 106   6.6 简单的程序 107   6.7 客户-服务器例子 112   6.8 复用消息 113   6.9 消息队列上使用select和poll 121   6.10 消息队列限制 122   6.11 小结 124   习题 124   第三部分 同步   第7章 互斥锁和条件变量 126   7.1 概述 126   7.2 互斥锁:上锁解锁 126   7.3 生产者-消费者问题 127   7.4 对比上锁等待 131   7.5 条件变量:等待信号发送 132   7.6 条件变量:定时等待和广播 136   7.7 互斥锁和条件变量的属性 136   7.8 小结 139   习题 139   第8章 读写锁 140   8.1 概述 140   8.2 获取释放读写锁 140   8.3 读写锁属性 141   8.4 使用互斥锁和条件变量实现读写锁 142   8.5 线程取消 148   8.6 小结 153   习题 153   第9章 记录上锁 154   9.1 概述 154   9.2 对比记录上锁文件上锁 157   9.3 Posix fcntl记录上锁 158   9.4 劝告性上锁 162   9.5 强制性上锁 164   9.6 读出者和写入者的优先级 166   9.7 启动个守护进程的唯副本 170   9.8 文件作锁用 171   9.9 NFS上锁 173   9.10 小结 173   习题 174   第10章 Posix信号量 175   10.1 概述 175   10.2 sem_open、sem_close和sem_   unlink函数 179   10.3 sem_wait和sem_trywait函数 180   10.4 sem_post和sem_getvalue函数 180   10.5 简单的程序 181   10.6 生产者-消费者问题 186   10.7 文件上锁 190   10.8 sem_init和sem_destroy函数 191   10.9 多个生产者,单个消费者 193   10.10 多个生产者,多个消费者 19
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值