⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 15.htm

📁 UNIX环境下C编程的详细详细介绍
💻 HTM
📖 第 1 页 / 共 5 页
字号:
<p>/* parse the arguments, set options */ </p>

<p>if (buf_args(buf, cli_args) &lt; 0) { </p>

<p>send_err(STDOUT_FILENO, -1, errmsg); </p>

<p>return; </p>

<p>} </p>

<p>if ( (newfd = open(pathname, oflag)) &lt; 0) { </p>

<p>sprintf(errmsg, &quot;can't open %s: %s\n&quot;, </p>

<p>pathname, strerror(errno)); </p>

<p>send_err(STDOUT_FILENO, -1, errmsg); </p>

<p>return; </p>

<p>} </p>

<p>/* send the descriptor */ </p>

<p>if (send_fd(STDOUT_FILENO, newfd) &lt; 0) </p>

<p>err_sys(&quot;send_fd error&quot;); </p>

<p>close(newfd); /* we're done with descriptor */ </p>

<p>} </p>

<p>程序15.16 request函数 </p>

<p>主函数(程序15.15)经流管道(它的标准输入)读来自客户的请求,然后调 
</p>

<p>用函数request。 </p>

<p>程序15.16中的request 函数承担全部工作。它调用函数buf_args将客户请求分解 
</p>

<p>成标准argv型的参数表,然后调用函数cli_args以处理客户的参数。如若一切正常 
</p>

<p>,则调用open 以打开相应文件,接着调用send_fd,经由流管道(它的标准输出) 
</p>

<p>将描述符回送给客户。如若出错则调用send_err回送一则出错消息,其中使用我们 
</p>

<p>在前面说明了的客户/服务器协议。 </p>

<p>#include &quot;ourhdr.h&quot; </p>

<p>#define MAXARGC 50 /* max number of arguments in buf */ </p>

<p>#define WHITE &quot; \t\n&quot; /* white space for tokenizing arguments */ </p>

<p>/* buf[] contains white-space separated arguments. We convert it </p>

<p>* to an argv[] style array of pointers, and call the user's </p>

<p>* function (*optfunc)() to process the argv[] array. </p>

<p>* We return -1 to the caller if there's a problem parsing buf, </p>

<p>* else we return whatever optfunc() returns. Note that user's </p>

<p>* buf[] array is modified (nulls placed after each token). */ </p>

<p>int </p>

<p>buf_args(char *buf, int (*optfunc)(int, char **)) </p>

<p>{ </p>

<p>char *ptr, *argv[MAXARGC]; </p>

<p>int argc; </p>

<p>if (strtok(buf, WHITE) == NULL) /* an argv[0] is required */ </p>

<p>return(-1); </p>

<p>argv[argc = 0] = buf; </p>

<p>while ( (ptr = strtok(NULL, WHITE)) != NULL) { </p>

<p>if (++argc &gt;= MAXARGC-1) /* -1 for room for NULL at end */ </p>

<p>return(-1); </p>

<p>argv[argc] = ptr; </p>

<p>} </p>

<p>argv[++argc] = NULL; </p>

<p>return( (*optfunc)(argc, argv) ); </p>

<p>/* Since argv[] pointers point into the user's buf[], </p>

<p>user's function can just copy the pointers, even </p>

<p>though argv[] array will disappear on return. */ </p>

<p>} </p>

<p>程序15.17 buf_args函数 </p>

<p>  buf_args调用的服务器函数是cli_args(程序15.18)。它验证客户发送的参 
</p>

<p>数数是否正确,然后将路径名和打开方式存放在全局变量中。 </p>

<p>  这样也就完成了open服务器,它由客户执行fork和exec而调用。在fork之前创 
</p>

<p>建了一个流管道,然后客户和服务器用其进行通信。在这种安排下,每个客户都有 
</p>

<p>一服务器。 </p>

<p>在下一节观察了客户一服务器连接后,我们将在15.6节重新实现一个open服务器, 
</p>

<p>其中用一个精灵进程作为服务器,所有客户都与其进行联系。 </p>

<p>#include &quot;opend.h&quot; </p>

<p>/* This function is called by buf_args(), which is called by </p>

<p>* request(). buf_args() has broken up the client's buffer </p>

<p>* into an argv[] style array, which we now process. */ </p>

<p>int </p>

<p>cli_args(int argc, char **argv) </p>

<p>{ </p>

<p>if (argc != 3 || strcmp(argv[0], CL_OPEN) != 0) { </p>

<p>strcpy(errmsg, &quot;usage: &lt;pathname&gt; &lt;oflag&gt;\n&quot;); </p>

<p>return(-1); </p>

<p>} </p>

<p>pathname = argv[1]; /* save ptr to pathname to open */ </p>

<p>oflag = atoi(argv[2]); </p>

<p>return(0); </p>

<p>} </p>

<p>程序15.18 cli_args函数 </p>

<p>15.5 客户-服务器连接函数 </p>

<p>对于相关进程(例如,父进程和子进程)之间的IPC,流管道是非常有用的。 
</p>

<p>前节所述的open服务器使用末命名的流管道能从子进程向父进程传送文件描述符。 
</p>

<p>但是当我们处理无关进程时(例如,若服务器是一精灵进程),则需要使用有名的 
</p>

<p>流管道。 </p>

<p>我们可以先构造一末名流管道(用s_pipe 
函数),然后对每一端加上一文件 </p>

<p>系统路径名。一精灵服务器进程将只创建流管道的一端,并对该端加上一名字。这 
</p>

<p>样,无关的客户可以向服务者的流管道端发送消息,从而与精灵进程会聚。这类似 
</p>

<p>于图14.12中所示的情况,在该图中客户使用FIFO发送它们的请求。 </p>

<p>一种更好的方法是:服务器创建一名字公开的流管道的一端,然后客户连接 
</p>

<p>_______________________________________________________________________ </p>

<p>______ </p>

<p>listenfd是serv_listen返回的描述符。在一客户连接到服务器的众所周知的 
</p>

<p>名字上之前,此函数并不返回。当客户连接至服务器时,自动创建一条全新的流管 
</p>

<p>道,其新描述符作为该函数的值返回。另外,客户的有效用户ID通过指针uidptr存 
</p>

<p>储。 </p>

<p>客户为与一服务器连接只需调用cli_conn函数。 </p>

<p>_______________________________________________________________________ </p>

<p>______ </p>

<p>#include &quot;ourhdr,h&quot; </p>

<p>int cli_conn(const char *name); </p>

<p>返回:若成功返回为文件描述符,出错&lt;0 </p>

<p>_______________________________________________________________________ </p>

<p>______ </p>

<p>客户指定的name应当与服务器调用serv_listen时宣布的相同。返回的描述符引用 
</p>

<p>连接至服务器的流管道 </p>

<p>使用上述三个函数,就可编写服务器精灵进程,它可以管理任意数量的客户。唯一 
</p>

<p>的限制是单个进程可用的描述符数,服务器对于每一个客户连接都需要一个描述符 
</p>

<p>。因为这些函数处理的都是普通文件描述符,所以服务器使用SELECT或POLL就可在 
</p>

<p>所有客户间多路转接I/O请求。最后,因为客户一服务器连接都是流管道,所以可 
</p>

<p>以经由连接传送打开描述符。 </p>

<p>在下面二节中,将说明在SVR4和4.3+BSD之下这三个函数的实现。在第十八章中当 
</p>

<p>我们开发一个通用的连接服务器时,也将使用这三个函数。 </p>

<p>15.5.1 SVR4 </p>

<p>SVR4提供装配的流以及一个名为connld的流处理模块,用其可以提供与服务器 
</p>

<p>有唯一连接的命名流管道。 </p>

<p>装配流和connld模块是由Presotto和Ritchie[1990]为RESEARCH UNIX系统 </p>

<p>开发 </p>

<p>的,后来由SVR4采用。 </p>

<p>首先,服务器创建一末名流管道,并将流处理模块connld压入一端。图15.5 
显示 </p>

<p>了这一处理结果。 </p>

<p>图15.5 在一端压入connld模块后的流管道 </p>

<p>然后,使压入connld的一端具有一路径名。SVR4提供fattach函数实现这一点。任 
</p>

<p>一进程(例如客户)打开此路径名就引用该管道的命名端。程序15.19使用了二十 
</p>

<p>余行代码实现serv_listen函数。 </p>

<p>#include &lt;sys/types.h&gt; </p>

<p>#include &lt;sys/stat.h&gt; </p>

<p>#include &lt;stropts.h&gt; </p>

<p>#include &quot;ourhdr.h&quot; </p>

<p>#define FIFO_MODE (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH) </p>

<p>/* user rw, group rw, others rw */ </p>

<p>int /* returns fd if all OK, &lt;0 on error */ </p>

<p>serv_listen(const char *name) </p>

<p>{ </p>

<p>int tempfd, fd[2], len; </p>

<p>/* create a file: mount point for fattach() */ </p>

<p>unlink(name); </p>

<p>if ( (tempfd = creat(name, FIFO_MODE)) &lt; 0) </p>

<p>return(-1); </p>

<p>if (close(tempfd) &lt; 0) </p>

<p>return(-2); </p>

<p>if (pipe(fd) &lt; 0) </p>

<p>return(-3); </p>

<p>/* push connld &amp; fattach() on fd[1] */ </p>

<p>if (ioctl(fd[1], I_PUSH, &quot;connld&quot;) &lt; 0) </p>

<p>return(-4); </p>

<p>if (fattach(fd[1], name) &lt; 0) </p>

<p>return(-5); </p>

<p>return(fd[0]); /* fd[0] is where client connections arrive */ </p>

<p>} </p>

<p>程序15.19 SVR4之下的serv_listen函数 </p>

<p>当另一进程对管道的命名端(connld模块压入端)调用open时,发生下列处 
</p>

<p>理过程: </p>

<p>1. 创建一个新管道。 </p>

<p>2. 该新管道的一个描述符作为open的返回值回送给客户。 </p>

<p>3. 另一个描述符在命名管道的另一端(亦即不是压入connld的端)传送给服务器 
</p>

<p>。服务器以带I-RECVED命令的ioctl接受该新描述符。 </p>

<p>假定,服务器用fattach函数加到其管道的众所周知是/tmp/serv1.图15.6显 
</p>

<p>示了客户调用 </p>

<p>fd=open(&quot;/tmp/serv1&quot;,O_RDWR); </p>

<p>并返回后产生的结果。 </p>

<p>图15.6 客户-服务器在一命名管道上的连接 </p>

<p>在客户和服务器之间的管道是open创建的,被打开的路径名实际上是一命名管道, 
</p>

<p>其中压入了connld模块。客户得到由open返回的文件描述符fd。在服务器处的新文 
</p>

<p>件描述符是clifdl,它是由服务器在描述符fd[1]上以I_RECVFD命令调用ioctl而接 
</p>

<p>收到的。一旦服务器在fd[1]上压入了connld模块,并对fd[1]附接上一个名字,它 
</p>

<p>就不再使用fd[1]。 </p>

<p>服务器调用程序15.20中的serv_accept函数等待客户连接到达。 </p>

<p>#include &lt;sys/types.h&gt; </p>

<p>#include &lt;sys/stat.h&gt; </p>

<p>#include &lt;stropts.h&gt; </p>

<p>#include &quot;ourhdr.h&quot; </p>

<p>/* Wait for a client connection to arrive, and accept it. </p>

<p>* We also obtain the client's user ID. */ </p>

<p>int /* returns new fd if all OK, -1 on error */ </p>

<p>serv_accept(int listenfd, uid_t *uidptr) </p>

<p>{ </p>

<p>struct strrecvfd recvfd; </p>

<p>if (ioctl(listenfd, I_RECVFD, &amp;recvfd) &lt; 0) </p>

<p>return(-1); /* could be EINTR if signal caught */ </p>

<p>if (uidptr != NULL) </p>

<p>*uidptr = recvfd.uid; /* effective uid of caller */ </p>

<p>return(recvfd.fd); /* return the new descriptor */ </p>

<p>} </p>

<p>程序15.20 用于SVR4的serv_accept函数 </p>

<p>在图15.6中,serv_accept的第一个参数应当是描述符fd[0],serv_accept的 </p>

<p>返回值是描述符clifdl。 </p>

<p>客户调用程序15.21中的cli_conn函数起动对服务器的连接。 </p>

<p>#include &lt;sys/types.h&gt; </p>

<p>#include &lt;sys/stat.h&gt; </p>

<p>#include &lt;fcntl.h&gt; </p>

<p>#include &quot;ourhdr.h&quot; </p>

<p>/* Create a client endpoint and connect to a server. */ </p>

<p>int /* returns fd if all OK, &lt;0 on error */ </p>

<p>cli_conn(const char *name) </p>

<p>{ </p>

<p>int fd; </p>

<p>/* open the mounted stream */ </p>

<p>if ( (fd = open(name, O_RDWR)) &lt; 0) </p>

<p>return(-1); </p>

<p>if (isastream(fd) == 0) </p>

<p>return(-2); </p>

<p>return(fd); </p>

<p>} </p>

<p>程序15.21 对于SVR4的cli_conn函数 </p>

<p>我们对返回的描述符是否引用一个流设备进行了两次检查,以便处理服务器没有起 
</p>

<p>动,但该路径名却存在于文件系统中的情况。(在SVR4下,几乎没有什么理由去调 
</p>

<p>用cli_conn,而不是直接调用open。在下一节我们将看到,在BSD系统之下,cli_ 
</p>

<p>conn函数要复杂得多,因此编写cli_conn函数就是必要的了。) </p>

<p>15.5.2 4.3+BSD </p>

<p>在4.3+BSD之下,为了用UNIX域套接口连接客户和服务器,我们需要有一套不同的 
</p>

<p>操作函数。因为应用socket、bind、listen、accept和connect函数的大部分细节 
</p>

<p>与其它网络协议有关(参见Stevens[1990]),所以此处不详细展开。 </p>

<p>因为SVR4也支持UNIX域套接口,所以本节所示代码同样可在SVR4之下工作 
</p>

<p>。 </p>

<p>程序15.22包含了serv_listen函数。它是服务器调用的第一个函数 </p>

<p>#include &lt;sys/types.h&gt; </p>

<p>#include &lt;sys/socket.h&gt; </p>

<p>#include &lt;sys/un.h&gt; </p>

<p>#include &quot;ourhdr.h&quot; </p>

<p>/* Create a server endpoint of a connection. */ </p>

<p>int /* returns fd if all OK, &lt;0 on error */ </p>

<p>serv_listen(const char *name) </p>

<p>{ </p>

<p>int fd, len; </p>

<p>struct sockaddr_un unix_addr; </p>

<p>/* create a Unix domain stream socket */ </p>

<p>if ( (fd = socket(AF_UNIX, SOCK_STREAM, 0)) &lt; 0) </p>

<p>return(-1); </p>

<p>unlink(name); /* in case it already exists */ </p>

<p>/* fill in socket address structure */ </p>

<p>memset(&amp;unix_addr, 0, sizeof(unix_addr)); </p>

<p>unix_addr.sun_family = AF_UNIX; </p>

<p>strcpy(unix_addr.sun_path, name); </p>

<p>#ifdef SCM_RIGHTS /* 4.3BSD Reno and later */ </p>

<p>len = sizeof(unix_addr.sun_len) + sizeof(unix_addr.sun_family) + </p>

<p>strlen(unix_addr.sun_path) + 1; </p>

<p>unix_addr.sun_len = len; </p>

<p>#else /* vanilla 4.3BSD */ </p>

<p>len = strlen(unix_addr.sun_path) + sizeof(unix_addr.sun_family); </p>

<p>#endif </p>

<p>/* bind the name to the descriptor */ </p>

<p>if (bind(fd, (struct sockaddr *) &amp;unix_addr, len) &lt; 0) </p>

<p>return(-2); </p>

<p>if (listen(fd, 5) &lt; 0) /* tell kernel we're a server */ </p>

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -