《UNIX网络编程 卷2》读书笔记(一)-阿里云开发者社区

开发者社区> 嗯哼9925> 正文

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

简介:
+关注继续查看
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,如需转载请自行联系原作者

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
阿里云服务器怎么设置密码?怎么停机?怎么重启服务器?
如果在创建实例时没有设置密码,或者密码丢失,您可以在控制台上重新设置实例的登录密码。本文仅描述如何在 ECS 管理控制台上修改实例登录密码。
9941 0
《UNIX网络编程 卷1:套接字联网API(第3版)》——1.8 BSD网络支持历史
源自Berkeley的最终版本是1994年的4.4BSD-Lite和1995年的4.4BSD-Lite2。我们指出这两个版本是其他多个系统(包括BSD/OS、FreeBSD、NetBSD和OpenBSD)的基础,这些系统大多数仍然处于活跃的开发和完善之中。
1622 0
带你读《C#神经网络编程》之二:构建第一个神经网络
本书旨在为C#程序员使用神经网络、CNTK等C#库和TensorFlowSharp解决复杂的计算问题时,提供实践指导。本书从神经网络入门知识开始,详细介绍如何使用Encog、Aforge和Accord搭建一个神经网络,帮助你深入理解神经网络相关概念和技术,例如深度网络、感知器、优化算法、卷积网络和自动解码器。此外,还详细讲解如何向.NET应用程序中添加智能特性,例如面部和运动检测、对象检测和标注、语言理解、知识和智能搜索。
1804 0
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,阿里云优惠总结大概有三种登录方式: 登录到ECS云服务器控制台 在ECS云服务器控制台用户可以更改密码、更换系.
13679 0
Python网络爬虫(Xpath解析, lxml库, selenium)
python、python爬虫、网络爬虫、爬虫框架、selenium、requests、urllib、数据分析、大数据、爬虫爬取静态网页、爬虫基础
2721 0
阿里云ECS云服务器初始化设置教程方法
阿里云ECS云服务器初始化是指将云服务器系统恢复到最初状态的过程,阿里云的服务器初始化是通过更换系统盘来实现的,是免费的,阿里云百科网分享服务器初始化教程: 服务器初始化教程方法 本文的服务器初始化是指将ECS云服务器系统恢复到最初状态,服务器中的数据也会被清空,所以初始化之前一定要先备份好。
11867 0
深入理解uwsgi和gunicorn网络模型
前言:        去年10月份建了一个python技术群,到现在为止人数已经涨到700人了。最一开始我经常在群里回应大家的问题,不管是简单还是困难的,我都会根据自己的经验来交流。
2639 0
阿里云ECS云服务器初始化设置教程方法
阿里云ECS云服务器初始化是指将云服务器系统恢复到最初状态的过程,阿里云的服务器初始化是通过更换系统盘来实现的,是免费的,阿里云百科网分享服务器初始化教程: 服务器初始化教程方法 本文的服务器初始化是指将ECS云服务器系统恢复到最初状态,服务器中的数据也会被清空,所以初始化之前一定要先备份好。
7308 0
+关注
4716
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
《2021云上架构与运维峰会演讲合集》
立即下载
《零基础CSS入门教程》
立即下载
《零基础HTML入门教程》
立即下载